From cc374a97610f27ff1ff391c557e0f0a0204c24a3 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sat, 24 Apr 2021 19:07:06 +0500 Subject: [PATCH 01/55] add desk --- src/desk/__init__.py | 0 src/desk/models.py | 45 ++++++++++++++++++++++++++++++++ src/desk/routes.py | 26 +++++++++++++++++++ src/desk/schemas.py | 61 ++++++++++++++++++++++++++++++++++++++++++++ src/desk/services.py | 36 ++++++++++++++++++++++++++ 5 files changed, 168 insertions(+) create mode 100644 src/desk/__init__.py create mode 100644 src/desk/models.py create mode 100644 src/desk/routes.py create mode 100644 src/desk/schemas.py create mode 100644 src/desk/services.py diff --git a/src/desk/__init__.py b/src/desk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/desk/models.py b/src/desk/models.py new file mode 100644 index 0000000..59f81e6 --- /dev/null +++ b/src/desk/models.py @@ -0,0 +1,45 @@ +import enum +from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, sql, Enum + +from ..db.db import Base + + +class StatusTasks(enum.Enum): + new = "New" + register = "Registered" + in_work = "In work" + closed = "Closed" + canceled = "Canceled" + reopen = "Reopen" + + +class Appeal(Base): + __tablename__ = 'appeal' + + id = Column(Integer, primary_key=True, index=True, unique=True) + topic = Column(String(100), nullable=False) + text = Column(String(500), nullable=False) + author = Column(String, ForeignKey('employee.id')) + date_create = Column(DateTime(timezone=True), server_default=sql.func.now(), nullable=False) + date_processing = Column(DateTime, default=None) + status = Column(Enum(StatusTasks), default=StatusTasks.new, nullable=False) + responsible = Column(String, ForeignKey('developer.id'), nullable=False) + software_id = Column(Integer, ForeignKey('software.id'), nullable=False) + module_id = Column(Integer, ForeignKey('module.id'), nullable=False) + # attachments + + # comments = relationship('Comment') + + +class Comment(Base): + __tablename__ = 'comment' + + id = Column(Integer, primary_key=True, index=True, unique=True) + text = Column(String(300), nullable=False) + appeal = Column(Integer, ForeignKey('appeal.id'), nullable=False) + author = Column(String, ForeignKey('employee.id'), nullable=False) + date_create = Column(DateTime(timezone=True), server_default=sql.func.now()) + + +appeals = Appeal.__table__ +comments = Comment.__table__ diff --git a/src/desk/routes.py b/src/desk/routes.py new file mode 100644 index 0000000..ed46e4c --- /dev/null +++ b/src/desk/routes.py @@ -0,0 +1,26 @@ +from fastapi import APIRouter +from typing import List +from .services import get_appeals, get_appeal, get_comments, get_comment +from .schemas import AppealShort, Appeal, CommentShort, Comment, AppealBase + +router = APIRouter() + + +@router.get("/", response_model=List[AppealBase]) +async def appeals_list(): + return await get_appeals() + + +@router.get("/{id}", response_model=AppealShort) +async def appeal(id: int): + return await get_appeal(id) + + +@router.get("/{id}/comments", response_model=List[CommentShort]) +async def comments_list(id: int): + return await get_comments(id) + + +@router.get("/{id}/comments/{pk}", response_model=Comment) +async def comment(id: int, pk: int): + return await get_comment(id, pk) diff --git a/src/desk/schemas.py b/src/desk/schemas.py new file mode 100644 index 0000000..a92b22b --- /dev/null +++ b/src/desk/schemas.py @@ -0,0 +1,61 @@ +from typing import List, Optional +from datetime import datetime +from .models import StatusTasks +from pydantic import BaseModel +from ..reference_book.schemas import SoftwareShort, ModuleShort + + +class AppealBase(BaseModel): + topic: str + text: str + status: StatusTasks + + +class AppealCreate(AppealBase): + author_id: str + responsible_id: str + software_id: int + module_id: int + + +class AppealShort(AppealBase): + id: int + author: int # TODO change to Member + date_create: datetime + date_processing: Optional[datetime] = None + responsible: int # TODO change to Developer + software: SoftwareShort + module: ModuleShort + + +class CommentBase(BaseModel): + text: str + + +class CommentCreate(CommentBase): + appeal_id: int + author_id: int + + +class Comment(CommentBase): + id: int + date_create: datetime + appeal: AppealShort + author: int # TODO maybe change to member (Union[member, developer]) + + +class CommentShort(CommentBase): + id: int + date_create: datetime + author: int # TODO maybe change to member (Union[member, developer]) + + +class Appeal(AppealBase): + id: int + author: int # TODO change to Member + date_create: datetime + date_processing: Optional[datetime] = None + responsible: int # TODO change to Developer + software: SoftwareShort + module: ModuleShort + # comments: List[CommentShort] = None diff --git a/src/desk/services.py b/src/desk/services.py new file mode 100644 index 0000000..db140c2 --- /dev/null +++ b/src/desk/services.py @@ -0,0 +1,36 @@ +from ..db.db import database +from .models import appeals, comments +from ..reference_book.models import softwares, modules + + +async def get_appeals(): + result = await database.fetch_all(query=appeals.select()) + return [dict(appeal) for appeal in result] + + +async def get_appeal(id: int): + query = appeals.select().where(appeals.c.id == id) + appeal = await database.fetch_one(query=query) + if appeal: + appeal = dict(appeal) + # author = await database.fetch_one(query=members.select().where(members.c.id == appeal["author"])) + # responsible = await database.fetch_one(query=developers.select().where(developers.c.id == appeal["responsible"])) + software = await database.fetch_one(query=softwares.select().where(softwares.c.id == appeal["software_id"])) + module = await database.fetch_one(query=modules.select().where(modules.c.id == appeal["module_id"])) + return {**appeal, "software": software, "module": module} + return None + + +async def get_comments(id: int): + query = comments.select().where(comments.c.appeal == id) + result = await database.fetch_all(query) + result = [dict(comment) for comment in result] + return result + + +async def get_comment(id: int, pk: int): + query = comments.select().where((comments.c.id == pk) & (comments.c.appeal == id)) + result = await database.fetch_one(query) + if result: + return dict(result) + return None From c70ddcc9a932571239f33cf73ab959cd2c54b371 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sat, 24 Apr 2021 19:07:54 +0500 Subject: [PATCH 02/55] add client --- src/client_account/__init__.py | 0 src/client_account/models.py | 22 ++++++++++++++++++++++ src/client_account/routers.py | 17 +++++++++++++++++ src/client_account/schemas.py | 26 ++++++++++++++++++++++++++ src/client_account/services.py | 18 ++++++++++++++++++ 5 files changed, 83 insertions(+) create mode 100644 src/client_account/__init__.py create mode 100644 src/client_account/models.py create mode 100644 src/client_account/routers.py create mode 100644 src/client_account/schemas.py create mode 100644 src/client_account/services.py diff --git a/src/client_account/__init__.py b/src/client_account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/client_account/models.py b/src/client_account/models.py new file mode 100644 index 0000000..5f0e90e --- /dev/null +++ b/src/client_account/models.py @@ -0,0 +1,22 @@ +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, sql + +from ..db.db import Base + + +class Client(Base): + __tablename__ = 'client' + + id = Column(Integer, primary_key=True, index=True, unique=True) + name = Column(String, unique=True, nullable=False) # TODO can be unique? + is_active = Column(Boolean, default=True, nullable=False) + date_create = Column(DateTime(timezone=True), server_default=sql.func.now()) + date_block = Column(DateTime, default=None, nullable=True) + owner_id = Column(String, ForeignKey('employee.id'), nullable=False) + # avatar + + # employees = relationship('Employee') + # licence = relationship('Licence') + # software = relationship('Software') + + +clients = Client.__table__ diff --git a/src/client_account/routers.py b/src/client_account/routers.py new file mode 100644 index 0000000..4f0c368 --- /dev/null +++ b/src/client_account/routers.py @@ -0,0 +1,17 @@ +from typing import List + +from fastapi import APIRouter +from .schemas import ClientShort, Client +from .services import get_clients, get_client + +router = APIRouter() + + +@router.get("/", response_model=List[ClientShort]) +async def clients_list(): + return await get_clients() + + +@router.get("/{id}", response_model=Client) +async def client(id: int): + return await get_client(id) diff --git a/src/client_account/schemas.py b/src/client_account/schemas.py new file mode 100644 index 0000000..896eec8 --- /dev/null +++ b/src/client_account/schemas.py @@ -0,0 +1,26 @@ +from datetime import datetime + +from pydantic import BaseModel +from ..users.schemas import Employee + + +class ClientBase(BaseModel): + name: str + date_block: datetime + + +class ClientCreate(ClientBase): + pass + + +class Client(ClientBase): + id: int + is_active: bool + date_create: datetime + owner: Employee + + +class ClientShort(ClientBase): + id: int + owner_id: str + date_create: datetime diff --git a/src/client_account/services.py b/src/client_account/services.py new file mode 100644 index 0000000..8a91a24 --- /dev/null +++ b/src/client_account/services.py @@ -0,0 +1,18 @@ +from ..db.db import database +from .models import clients +from ..users.models import employees + + +async def get_clients(): + result = await database.fetch_all(clients.select()) + return [dict(client) for client in result] + + +async def get_client(id: int): + query = clients.select().where(clients.c.id == id) + result = await database.fetch_one(query=query) + if result: + result = dict(result) + query = employees.select().where(employees.c.id == result["owner_id"]) + owner = await database.fetch_one(query=query) + return {**result, "owner": owner} From 3ddf979425fc88d400962989ad46a6f91b3d24c3 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sat, 24 Apr 2021 19:08:19 +0500 Subject: [PATCH 03/55] add reference book --- src/reference_book/__init__.py | 0 src/reference_book/api/__init__.py | 0 src/reference_book/api/licence.py | 32 +++++++++ src/reference_book/api/module.py | 32 +++++++++ src/reference_book/api/routes.py | 11 +++ src/reference_book/api/software.py | 38 ++++++++++ src/reference_book/models.py | 35 ++++++++++ src/reference_book/schemas.py | 81 ++++++++++++++++++++++ src/reference_book/services.py | 107 +++++++++++++++++++++++++++++ 9 files changed, 336 insertions(+) create mode 100644 src/reference_book/__init__.py create mode 100644 src/reference_book/api/__init__.py create mode 100644 src/reference_book/api/licence.py create mode 100644 src/reference_book/api/module.py create mode 100644 src/reference_book/api/routes.py create mode 100644 src/reference_book/api/software.py create mode 100644 src/reference_book/models.py create mode 100644 src/reference_book/schemas.py create mode 100644 src/reference_book/services.py diff --git a/src/reference_book/__init__.py b/src/reference_book/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/reference_book/api/__init__.py b/src/reference_book/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/reference_book/api/licence.py b/src/reference_book/api/licence.py new file mode 100644 index 0000000..6512333 --- /dev/null +++ b/src/reference_book/api/licence.py @@ -0,0 +1,32 @@ +from typing import List + +from fastapi import APIRouter +from ..schemas import Licence, LicenceShort, LicenceCreate +from ..services import get_licence, get_licences, add_licence, delete_licence, update_licence + +router = APIRouter() + + +@router.get("/", response_model=List[LicenceShort]) +async def licence_list(): + return await get_licences() + + +@router.get('/{id}', response_model=Licence) +async def licence(id: int): + return await get_licence(id) + + +@router.post("/") +async def create_licence(licence: LicenceCreate): + return await add_licence(licence) + + +@router.delete("/{id}") +async def delete_licence(id: int): + return await delete_licence(id) + + +@router.put("/{id}") +async def update_licence(id: int, item: LicenceCreate): + return await update_licence(id, item) diff --git a/src/reference_book/api/module.py b/src/reference_book/api/module.py new file mode 100644 index 0000000..406f94d --- /dev/null +++ b/src/reference_book/api/module.py @@ -0,0 +1,32 @@ +from typing import List + +from fastapi import APIRouter +from ..schemas import Module, ModuleShort, ModuleCreate +from ..services import get_module, get_modules, add_module, delete_module, update_module + +router = APIRouter() + + +@router.get("/", response_model=List[ModuleShort]) +async def module_list(): + return await get_modules() + + +@router.get('/{id}', response_model=Module) +async def module(id: int): + return await get_module(id) + + +@router.post("/") +async def create_module(item: ModuleCreate): + return await add_module(item) + + +@router.delete("/{id}") +async def delete_module(id: int): + return await delete_module(id) + + +@router.put("/{id}") +async def update_module(id: int, item: ModuleCreate): + return await update_module(id, item) diff --git a/src/reference_book/api/routes.py b/src/reference_book/api/routes.py new file mode 100644 index 0000000..e87a839 --- /dev/null +++ b/src/reference_book/api/routes.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter +from .module import router as module_router +from .software import router as software_router +from .licence import router as licence_router + +router = APIRouter() + + +router.include_router(module_router, prefix='/modules') +router.include_router(licence_router, prefix='/licences') +router.include_router(software_router, prefix='/software') diff --git a/src/reference_book/api/software.py b/src/reference_book/api/software.py new file mode 100644 index 0000000..acd73ea --- /dev/null +++ b/src/reference_book/api/software.py @@ -0,0 +1,38 @@ +from typing import List + +from fastapi import APIRouter +from ..services import get_software, get_softwares, get_software_with_modules, \ + add_software, delete_software, update_software +from ..schemas import Software, SoftwareShort, SoftwareCreate + +router = APIRouter() + + +@router.get('/', response_model=List[SoftwareShort]) +async def software_list(): + return await get_softwares() + + +@router.get('/{id}', response_model=SoftwareShort) +async def software(id: int): + return await get_software(id) + + +@router.get("/{id}/modules", response_model=Software) +async def get_software_modules(id: int): + return await get_software_with_modules(id) + + +@router.post("/") +async def create_licence(software: SoftwareCreate): + return await add_software(software) + + +@router.delete("/{id}") +async def delete_software(id: int): + return await delete_software(id) + + +@router.put("/{id}") +async def update_software(id: int, item: SoftwareCreate): + return await update_software(id, item) diff --git a/src/reference_book/models.py b/src/reference_book/models.py new file mode 100644 index 0000000..1a8cd59 --- /dev/null +++ b/src/reference_book/models.py @@ -0,0 +1,35 @@ +from sqlalchemy import Column, DateTime, String, Integer, sql, ForeignKey, UniqueConstraint + +from ..db.db import Base + + +class Licence(Base): + __tablename__ = 'licence' + + id = Column(Integer, primary_key=True, index=True, unique=True) + number = Column(Integer, unique=True, nullable=False) + count_members = Column(Integer, default=0, nullable=False) + date_end = Column(DateTime(timezone=True), server_default=sql.func.now()) + client_id = Column(Integer, ForeignKey('client.id'), nullable=False) + software_id = Column(Integer, ForeignKey('software.id'), nullable=False) + + +class Module(Base): + __tablename__ = 'module' + + id = Column(Integer, primary_key=True, index=True, unique=True) + name = Column(String, nullable=False) + software_id = Column(Integer, ForeignKey('software.id'), nullable=False) # TODO сделать проверку на ключ > 0 + UniqueConstraint(name, software_id) + + +class Software(Base): + __tablename__ = 'software' + + id = Column(Integer, primary_key=True, index=True, unique=True) + name = Column(String, unique=True, nullable=False) # TODO сделать проверку на непустую строку + + +modules = Module.__table__ +licences = Licence.__table__ +softwares = Software.__table__ \ No newline at end of file diff --git a/src/reference_book/schemas.py b/src/reference_book/schemas.py new file mode 100644 index 0000000..db8c972 --- /dev/null +++ b/src/reference_book/schemas.py @@ -0,0 +1,81 @@ +from datetime import datetime +from typing import List, Optional + +from pydantic import BaseModel + +from src.client_account.schemas import ClientShort + + +class SoftwareBase(BaseModel): + name: str = '' + + +class SoftwareCreate(SoftwareBase): + pass + + +class SoftwareShort(SoftwareBase): + id: int + + class Config: + orm_mode = True + + +class LicenceBase(BaseModel): + number: int + count_members: int + date_end: datetime + + +class LicenceCreate(LicenceBase): + client_id: int + software_id: int + + +class LicenceShort(LicenceBase): + id: int + client_id: int # TODO заменить на Client + software_id: int # TODO заменить на str + + class Config: + orm_mode = True + + +class Licence(LicenceBase): + id: int + client_id: ClientShort + software: SoftwareShort + + class Config: + orm_mode = True + + +class ModuleBase(BaseModel): + name: str + + +class ModuleCreate(ModuleBase): + software_id: int + + +class Module(ModuleBase): + id: int + software: SoftwareShort + + class Config: + orm_mode = True + + +class ModuleShort(ModuleBase): + id: int + + class Config: + orm_mode = True + + +class Software(SoftwareBase): + id: int + modules: List[ModuleShort] = None + + class Config: + orm_mode = True diff --git a/src/reference_book/services.py b/src/reference_book/services.py new file mode 100644 index 0000000..49b7151 --- /dev/null +++ b/src/reference_book/services.py @@ -0,0 +1,107 @@ +from datetime import datetime + +from .schemas import ModuleCreate, LicenceCreate, LicenceShort, SoftwareCreate +from ..client_account.services import get_client +from ..db.db import database +from .models import softwares, modules, licences +from sqlalchemy.sql import select + + +async def get_softwares(): + result = await database.fetch_all(query=softwares.select()) + return [dict(software) for software in result] + + +async def get_software(id: int): + result = await database.fetch_one(query=softwares.select().where(softwares.c.id == id)) + if result is not None: + return dict(result) + return None + + +async def add_software(software: SoftwareCreate): + query = softwares.insert().values(**software.dict()) + id = await database.execute(query) + return {**software.dict(), "id": id} + + +async def add_licence(licence: LicenceCreate): + query = licences.insert().values(**licence.dict()) + id = await database.execute(query) + return {**licence.dict(), "id": id} + + +async def add_module(module: ModuleCreate): + query = modules.insert().values(**module.dict()) + id = await database.execute(query) + return {**module.dict(), "id": id} + + +async def get_software_with_modules(id: int): + result = await database.fetch_one(query=softwares.select().where(softwares.c.id == id)) + query = select([modules.c.id, modules.c.name]).select_from(modules).where(modules.c.software_id == id) + module = await database.fetch_all(query) + if result is not None: + result = dict(result) + module = [dict(m) for m in module] + return {**result, "modules": module} + return None + + +async def get_modules(): + result = await database.fetch_all(query=modules.select()) + return [dict(module) for module in result] + + +async def get_module(id: int): + result = await database.fetch_one(query=modules.select().where(modules.c.id == id)) + if result is not None: + module = dict(result) + software = await get_software(module["software_id"]) + return {**module, "software": software} + return None + + +async def get_licences(): + result = await database.fetch_all(query=licences.select()) + return [dict(licence) for licence in result] + + +async def get_licence(id: int): + result = await database.fetch_one(query=licences.select().where(licences.c.id == id)) + if result is not None: + licence = dict(result) + client = await get_client(licence["client_id"]) + software = await get_software(licence["software_id"]) + return {**licence, "client": client, "software": software} + return None + + +async def update_module(id: int, module: ModuleCreate): + query = modules.update().where(modules.c.id == id).values(**module.dict()) + return await database.execute(query) + + +async def update_licence(id: int, licence: LicenceCreate): + query = licences.update().where(licences.c.id == id).values(**licence.dict()) + return await database.execute(query) + + +async def update_software(id: int, software: SoftwareCreate): + query = softwares.update().where(softwares.c.id == id).values(**software.dict()) + return await database.execute(query) + + +async def delete_module(id: int): + query = modules.delete().where(modules.c.id == id) + return await database.execute(query) + + +async def delete_licence(id: int): + query = licences.delete().where(licences.c.id == id) + return await database.execute(query) + + +async def delete_software(id: int): + query = softwares.delete().where(softwares.c.id == id) + return await database.execute(query) From d04f2d235b00f90f9f12d03894574f213f2a0755 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sat, 24 Apr 2021 19:08:45 +0500 Subject: [PATCH 04/55] add app file --- src/__init__.py | 0 src/app.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/__init__.py create mode 100644 src/app.py diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/app.py b/src/app.py new file mode 100644 index 0000000..fd7027e --- /dev/null +++ b/src/app.py @@ -0,0 +1,32 @@ +from fastapi import FastAPI + +from src.db.db import database, engine +from .db.base import Base +from .client_account.routers import router as client_router +from .desk.routes import router as desk_router +from .reference_book.api.routes import router as book_router +from .users.routes import router as users_routes + +Base.metadata.create_all(bind=engine) +app = FastAPI() + + +@app.on_event('startup') +async def startup(): + await database.connect() + + +@app.on_event('shutdown') +async def shutdown(): + await database.disconnect() + + +@app.get('/') +def read_root(): + return {'Hello': 'World'} + + +app.include_router(client_router, prefix='/client', tags=['client']) +app.include_router(book_router, prefix='/reference', tags=['Reference book']) +app.include_router(desk_router, prefix='/desk', tags=['Desk']) +app.include_router(users_routes) From e33a48127b7870cce989e1155855d0ffe201b999 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sat, 24 Apr 2021 20:09:50 +0500 Subject: [PATCH 05/55] add a db connection --- src/db/__init__.py | 0 src/db/base.py | 5 +++++ src/db/db.py | 15 +++++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 src/db/__init__.py create mode 100644 src/db/base.py create mode 100644 src/db/db.py diff --git a/src/db/__init__.py b/src/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/db/base.py b/src/db/base.py new file mode 100644 index 0000000..5756950 --- /dev/null +++ b/src/db/base.py @@ -0,0 +1,5 @@ +from .db import Base +from ..client_account import models +from ..reference_book import models +from ..desk import models +from ..users import models diff --git a/src/db/db.py b/src/db/db.py new file mode 100644 index 0000000..7a52fb5 --- /dev/null +++ b/src/db/db.py @@ -0,0 +1,15 @@ +import databases +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base + + +SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" +# SQLALCHEMY_DATABASE_URL = "sqlite:///./pre-production.db" +# SQLALCHEMY_DATABASE_URL = "sqlite:///./production.db" + +engine = create_engine( + SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} +) + +database = databases.Database(SQLALCHEMY_DATABASE_URL) +Base: DeclarativeMeta = declarative_base() From 2637e9213b571eecd35ad0e651a4ad691ba380a3 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sat, 24 Apr 2021 20:10:35 +0500 Subject: [PATCH 06/55] add employees --- src/users/__init__.py | 0 src/users/logic.py | 52 +++++++++++++++++++++++++++++++++++++++++ src/users/models.py | 35 ++++++++++++++++++++++++++++ src/users/routes.py | 41 ++++++++++++++++++++++++++++++++ src/users/schemas.py | 53 ++++++++++++++++++++++++++++++++++++++++++ test.db | Bin 118784 -> 118784 bytes 6 files changed, 181 insertions(+) create mode 100644 src/users/__init__.py create mode 100644 src/users/logic.py create mode 100644 src/users/models.py create mode 100644 src/users/routes.py create mode 100644 src/users/schemas.py diff --git a/src/users/__init__.py b/src/users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/users/logic.py b/src/users/logic.py new file mode 100644 index 0000000..8f23146 --- /dev/null +++ b/src/users/logic.py @@ -0,0 +1,52 @@ +from typing import Union + +from fastapi_users.authentication import JWTAuthentication +from fastapi_users import FastAPIUsers +from requests import Request + +from ..config import SECRET +from .schemas import Employee, EmployeeCreate, EmployeeUpdate, EmployeeDB, \ + Developer, DeveloperCreate, DeveloperUpdate, DeveloperDB +from .models import employee_db, developer_db + +auth_backends = [] + +jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600) + +auth_backends.append(jwt_authentication) + + +employee_users = FastAPIUsers( + employee_db, + auth_backends, + Employee, + EmployeeCreate, + EmployeeUpdate, + EmployeeDB, +) + + +developer_users = FastAPIUsers( + developer_db, + auth_backends, + Developer, + DeveloperCreate, + DeveloperUpdate, + DeveloperDB, +) + + +async def on_after_forgot_password(user: Union[EmployeeDB, DeveloperDB], token: str, request: Request): + print(f"User {user.id} has forgot their password. Reset token: {token}") + + +async def on_after_reset_password(user: Union[EmployeeDB, DeveloperDB], request: Request): + print(f"User {user.id} has reset their password.") + + +async def after_verification_request(user: Union[EmployeeDB, DeveloperDB], token: str, request: Request): + print(f"Verification requested for user {user.id}. Verification token: {token}") + + +async def after_verification(user: Union[EmployeeDB, DeveloperDB], request: Request): + print(f"{user.id} is now verified.") diff --git a/src/users/models.py b/src/users/models.py new file mode 100644 index 0000000..02a621a --- /dev/null +++ b/src/users/models.py @@ -0,0 +1,35 @@ +# from tokenize import String + +from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase +from sqlalchemy import Column, Integer, Boolean, ForeignKey, DateTime, sql, String + +from .schemas import EmployeeDB, DeveloperDB +from ..db.db import database, Base + + +class EmployeeTable(Base, SQLAlchemyBaseUserTable): + __tablename__ = 'employee' + + name = Column(String, nullable=False) + surname = Column(String, nullable=False) + patronymic = Column(String, nullable=True) + is_owner = Column(Boolean, default=False, nullable=False) + client = Column(Integer, ForeignKey('client.id')) + date_reg = Column(DateTime(timezone=True), server_default=sql.func.now()) + date_block = Column(DateTime, default=None, nullable=True) + # avatar + + +class DeveloperTable(Base, SQLAlchemyBaseUserTable): + __tablename__ = 'developer' + + name = Column(String, nullable=False) + surname = Column(String, nullable=False) + patronymic = Column(String, nullable=True) + # avatar + + +employees = EmployeeTable.__table__ +employee_db = SQLAlchemyUserDatabase(EmployeeDB, database, employees) +developers = DeveloperTable.__table__ +developer_db = SQLAlchemyUserDatabase(DeveloperDB, database, developers) diff --git a/src/users/routes.py b/src/users/routes.py new file mode 100644 index 0000000..5c69036 --- /dev/null +++ b/src/users/routes.py @@ -0,0 +1,41 @@ +from fastapi import Depends, Response +from fastapi import APIRouter +from ..config import SECRET + +from src.users.logic import jwt_authentication, employee_users, on_after_forgot_password, on_after_reset_password, \ + after_verification, after_verification_request + +router = APIRouter() + + +@router.post("/auth/jwt/refresh") +async def refresh_jwt(response: Response, user=Depends(employee_users.get_current_active_user)): + return await jwt_authentication.get_login_response(user, response) + + +router.include_router( + employee_users.get_auth_router(jwt_authentication), + prefix="/auth/jwt", + tags=["auth"]) +router.include_router( + employee_users.get_register_router(), + prefix="/auth", + tags=["auth"]) +router.include_router( + employee_users.get_reset_password_router( + SECRET, + after_forgot_password=on_after_forgot_password, + after_reset_password=on_after_reset_password), + prefix="/auth", + tags=["auth"]) +router.include_router( + employee_users.get_users_router(), + prefix="/users", + tags=["users"]) +router.include_router( + employee_users.get_verify_router( + SECRET, + after_verification_request=after_verification_request, + after_verification=after_verification), + prefix="/auth", + tags=["auth"]) diff --git a/src/users/schemas.py b/src/users/schemas.py new file mode 100644 index 0000000..d53e961 --- /dev/null +++ b/src/users/schemas.py @@ -0,0 +1,53 @@ +from datetime import datetime + +from fastapi_users import models +from pydantic import validator +from typing import Optional + + +class Employee(models.BaseUser): + name: str + surname: str + patronymic: Optional[str] + is_owner: bool + client: int + date_reg: datetime + date_block: datetime + + +class EmployeeCreate(models.BaseUserCreate): + @validator('password') + def valid_password(cls, v: str): + if len(v) < 6: + raise ValueError('Password should be at least 6 characters') + return v + + +class EmployeeUpdate(Employee, models.BaseUserUpdate): + pass + + +class EmployeeDB(Employee, models.BaseUserDB): + pass + + +class Developer(models.BaseUser): + name: str + surname: str + patronymic: Optional[str] + + +class DeveloperCreate(models.BaseUserCreate): + @validator('password') + def valid_password(cls, v: str): + if len(v) < 6: + raise ValueError('Password should be at least 6 characters') + return v + + +class DeveloperUpdate(Developer, models.BaseUserUpdate): + pass + + +class DeveloperDB(Developer, models.BaseUserDB): + pass diff --git a/test.db b/test.db index 56f26de3143a78dd41bb3160d7a5f4e3daaba6d4..c8323907ef8001412a4af24495f8085cc21f100c 100644 GIT binary patch delta 905 zcmcJO&1(}u7{=$_q?yg^XCXC7lO?hBVC_fJHZ2qnNe)HSCMhZiB9_uF8c3^%mU_{O zB4|By8FcEYsfTDFO^p#ODG2@nwR#doLA~g~o1P4!vqg#(5idRbhUcBR%=64MGkH9d z$8&+k351Zr@o=1+gw@eIFoC+_uNwFSTAi=RuhpfPgvp94(D1q8j_^UCkVbXcQZAs9 z=pvVJL0i+-P{~SqDy1(jH+g_?#DEjMbP?}j&&uGVZ*dE`I@OK6h5^^(?EMNfflwV5 zTY!z%z);6>S#$DScz^oH;J(2_^rn00_C4^h<7EiZ1B9?AH5jl3EfAfaBs=IZ_Blyg zq=$moB{|dz678#|T3P237}b~LwS)VCAX_TKQQO&9s%sHjJZ0iC+z`6^sH%0?F7@`i z`n#Bd7my@Sq;@IamBN4fwMs))(&xkr@R7YJ!a-ZjSAOJOZrO79{}O_12@kg^1ljsC zgc>;23N%%3$w9%6BlGV#qM$tj9PM=QRDvA48G+x>xz;#@v#B4)qVPP^Ea4(jSJhL> zjB;3Ul38-h{Y^}YDlS$V^;x`UtRH*3LvY!Vog6o(v(qEv=9HN`ea1XHVor=^bDLu` zTQ9@T;KpV+lMZhlgm-W3wQamyRMM{W$FMgPf@^!3x(r?*bW}6g`TMX+FG_xzaQDzR x+SUs7@ycv%Wh?w@&^DAa6=my1NV(c0E^pk=5{vvFFh!%5{Hao(hr%2regebQ^mG6K delta 271 zcmZozz}~QceS)-LIs*fPED(zTF&hwDP1G^AOlQ!uzsU>ai7~8U;J4@J;oHck$NQIe z8Mg^HJJ${l1NQ&y^Vy_X->}YPdc-uD(U0Lb&qUrzhBXr%MK-Sa%*GU_HvLa0qY9(I zbj2=4D@N7Hja+)%{H?4i?BcSrjGeL5AMRijW#rnvx{FbXaXR}hM#0I^Y`l}-GqO%+ z>|u1Cp0bPa0h6!tbltU#vYW4S-C~};avftdlOfY|feuCkM&{`(>lqI&5_qskL12;t z%T^XfW&>STpnGM27~~r%AeIDTX&~kUVvt{WfLIKOMS)m$yNLng1^>+~0)OO}EnqYN E0N{H-f&c&j From e10673e3b1a364964239168b287902944a933f27 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sun, 25 Apr 2021 12:28:44 +0500 Subject: [PATCH 07/55] change host for docker --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b78ebc1..4804913 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ win_format: up: - uvicorn $(CODE).app:app --reload + uvicorn $(CODE).app:app --host=0.0.0.0 --reload ci: lint test From cbf5a7f28f5ec97061d95e6cbb1d1d18c2cd16fa Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sun, 25 Apr 2021 12:29:20 +0500 Subject: [PATCH 08/55] add routers for developers and fix reg --- src/users/models.py | 4 +--- src/users/routes.py | 35 +++++++++++++++++++++++++++++++++-- src/users/schemas.py | 18 +++++++++++++++--- test.db | Bin 118784 -> 118784 bytes 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/users/models.py b/src/users/models.py index 02a621a..ebca9e7 100644 --- a/src/users/models.py +++ b/src/users/models.py @@ -1,5 +1,3 @@ -# from tokenize import String - from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase from sqlalchemy import Column, Integer, Boolean, ForeignKey, DateTime, sql, String @@ -14,7 +12,7 @@ class EmployeeTable(Base, SQLAlchemyBaseUserTable): surname = Column(String, nullable=False) patronymic = Column(String, nullable=True) is_owner = Column(Boolean, default=False, nullable=False) - client = Column(Integer, ForeignKey('client.id')) + client = Column(Integer, ForeignKey('client.id')) # TODO сделать проверку на существующего владельца клиента date_reg = Column(DateTime(timezone=True), server_default=sql.func.now()) date_block = Column(DateTime, default=None, nullable=True) # avatar diff --git a/src/users/routes.py b/src/users/routes.py index 5c69036..3de4d92 100644 --- a/src/users/routes.py +++ b/src/users/routes.py @@ -2,8 +2,8 @@ from fastapi import APIRouter from ..config import SECRET -from src.users.logic import jwt_authentication, employee_users, on_after_forgot_password, on_after_reset_password, \ - after_verification, after_verification_request +from src.users.logic import jwt_authentication, employee_users, developer_users, \ + on_after_forgot_password, on_after_reset_password, after_verification, after_verification_request router = APIRouter() @@ -39,3 +39,34 @@ async def refresh_jwt(response: Response, user=Depends(employee_users.get_curren after_verification=after_verification), prefix="/auth", tags=["auth"]) + + +router.include_router( + developer_users.get_auth_router(jwt_authentication), + prefix="/developers/auth/jwt", + tags=["dev_auth"]) +router.include_router( + developer_users.get_register_router(), + prefix="/developers/auth", + tags=["dev_auth"]) +router.include_router( + developer_users.get_reset_password_router( + SECRET, + after_forgot_password=on_after_forgot_password, + after_reset_password=on_after_reset_password), + prefix="/developers/auth", + tags=["dev_auth"]) +router.include_router( + developer_users.get_users_router(), + prefix="/developers", + tags=["developers"]) +router.include_router( + developer_users.get_verify_router( + SECRET, + after_verification_request=after_verification_request, + after_verification=after_verification), + prefix="/developers/auth", + tags=["dev_auth"]) + + +# router.include_router(employee_users.router, prefix="/users", tags=["test_user"]) diff --git a/src/users/schemas.py b/src/users/schemas.py index d53e961..b337bf9 100644 --- a/src/users/schemas.py +++ b/src/users/schemas.py @@ -11,11 +11,18 @@ class Employee(models.BaseUser): patronymic: Optional[str] is_owner: bool client: int - date_reg: datetime - date_block: datetime + date_block: Optional[datetime] class EmployeeCreate(models.BaseUserCreate): + name: str + surname: str + patronymic: Optional[str] + is_owner: bool = False + client: int + date_reg: datetime # TODO попробовать удалить данное поле из регистрации. Пока без нее не работет + date_block: Optional[datetime] + @validator('password') def valid_password(cls, v: str): if len(v) < 6: @@ -28,16 +35,21 @@ class EmployeeUpdate(Employee, models.BaseUserUpdate): class EmployeeDB(Employee, models.BaseUserDB): - pass + date_reg: datetime class Developer(models.BaseUser): name: str surname: str patronymic: Optional[str] + is_superuser: Optional[bool] = True class DeveloperCreate(models.BaseUserCreate): + name: str + surname: str + patronymic: Optional[str] + @validator('password') def valid_password(cls, v: str): if len(v) < 6: diff --git a/test.db b/test.db index c8323907ef8001412a4af24495f8085cc21f100c..ec45bc548b773bff0366745543683a001bb0f75d 100644 GIT binary patch delta 822 zcma))%TC)s6o&02l(?5nP{ksGib#OUW`bvY8`BL*pdrnLKx#pdDiJ$&Facxim?jZY z$Q!ik3q)1hEo$fk^dY+Fl5Kabc!8RLP^nsp#f(PhXmtMX|IbN(^rS!fW`efgPNr%5 z^Y#Z24n&SAkcu3ijYaK9x0en8WwtlMgu_o_F@{Mdd7WSuvntd@4MCpg4Jc#DfFkBq z4GWx%knFj-gBI;}LOa->!RidcGg)F8GnHZ|Uux7FZf(=u z$<>zT@!`w)*7}RBoMINc)qUOakK1%itA2I+(-CBOh=n*SEnr~*@d-}AEbFWKU#Its z2FmmhWBlU`~LjN_5CeScb%?ivCN+q{<>q5`B!W?%c+x?yN1fjY_ALNp7@@ zN8(C`bqYFHBbEKqVM|Zs=l52MI+wPV%~IQu^0{;)Ar_N`<+8H=FZqZpU@0+F|Cjt> zXY9IlpQ5M$IHkbpS@i2i@M} z=CW{Y$opt~upMIW(V#_vmjA~B-QEcOK$-GwX2RnWF+pQh(I5sCkg7snRtY3nU?HdT zhFlXxL#rXXecNAm!<3J(w^rw@{BEYQl}e>6omErw6#F1wSP`Df4bp5Pp}zOJnOn=2 zlG)UzSoAlf>^T;5vnhjqXfZ^C@04%X7w|3~ot(RL;x38?KSO;V>Vg6nKGYBJ=wWb* Lxsmy)PqV)P@YUer delta 63 zcmZozz}~QceS$P&?nD`9#@vkwOZYdlIQ)^{6u=^|n8hIg$YE00%(mg5{$`edzx Date: Sun, 25 Apr 2021 21:07:18 +0500 Subject: [PATCH 09/55] change setup db --- .gitignore | 7 ++++++- src/db/db.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8cb5f06..e29c056 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,9 @@ __pycache__/ *.pyc *.pyo *.pyd -*.local \ No newline at end of file +*.local + +# database +test.db +pre-production.db +production.db diff --git a/src/db/db.py b/src/db/db.py index 7a52fb5..37d37db 100644 --- a/src/db/db.py +++ b/src/db/db.py @@ -3,8 +3,8 @@ from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base -SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" -# SQLALCHEMY_DATABASE_URL = "sqlite:///./pre-production.db" +# SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" +SQLALCHEMY_DATABASE_URL = "sqlite:///./pre-production.db" # SQLALCHEMY_DATABASE_URL = "sqlite:///./production.db" engine = create_engine( From f22cc7e07e3d01544bef8de516e52fd307b6dc27 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sun, 25 Apr 2021 21:07:46 +0500 Subject: [PATCH 10/55] add tests --- src/reference_book/api/licence.py | 18 +-- tests/__init__.py | 0 tests/test_home.py | 12 ++ tests/tests_clients/__init__.py | 0 tests/tests_clients/test_add.py | 0 tests/tests_clients/test_delete.py | 0 tests/tests_clients/test_get.py | 0 tests/tests_clients/test_update.py | 0 tests/tests_desk/__init__.py | 0 tests/tests_desk/test_add.py | 0 tests/tests_desk/test_delete.py | 0 tests/tests_desk/test_get.py | 0 tests/tests_desk/test_routers.py | 0 tests/tests_desk/test_update.py | 0 tests/tests_reference_book/__init__.py | 0 tests/tests_reference_book/test_add.py | 109 +++++++++++++++++++ tests/tests_reference_book/test_delete.py | 0 tests/tests_reference_book/test_get.py | 99 +++++++++++++++++ tests/tests_reference_book/test_routes.py | 127 ++++++++++++++++++++++ tests/tests_reference_book/test_update.py | 0 tests/tests_users/__init__.py | 0 tests/tests_users/test_add.py | 0 tests/tests_users/test_delete.py | 0 tests/tests_users/test_get.py | 0 tests/tests_users/test_update.py | 0 25 files changed, 356 insertions(+), 9 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/test_home.py create mode 100644 tests/tests_clients/__init__.py create mode 100644 tests/tests_clients/test_add.py create mode 100644 tests/tests_clients/test_delete.py create mode 100644 tests/tests_clients/test_get.py create mode 100644 tests/tests_clients/test_update.py create mode 100644 tests/tests_desk/__init__.py create mode 100644 tests/tests_desk/test_add.py create mode 100644 tests/tests_desk/test_delete.py create mode 100644 tests/tests_desk/test_get.py create mode 100644 tests/tests_desk/test_routers.py create mode 100644 tests/tests_desk/test_update.py create mode 100644 tests/tests_reference_book/__init__.py create mode 100644 tests/tests_reference_book/test_add.py create mode 100644 tests/tests_reference_book/test_delete.py create mode 100644 tests/tests_reference_book/test_get.py create mode 100644 tests/tests_reference_book/test_routes.py create mode 100644 tests/tests_reference_book/test_update.py create mode 100644 tests/tests_users/__init__.py create mode 100644 tests/tests_users/test_add.py create mode 100644 tests/tests_users/test_delete.py create mode 100644 tests/tests_users/test_get.py create mode 100644 tests/tests_users/test_update.py diff --git a/src/reference_book/api/licence.py b/src/reference_book/api/licence.py index 6512333..ae0dd1d 100644 --- a/src/reference_book/api/licence.py +++ b/src/reference_book/api/licence.py @@ -1,32 +1,32 @@ from typing import List -from fastapi import APIRouter -from ..schemas import Licence, LicenceShort, LicenceCreate +from fastapi import APIRouter, status +from ..schemas import Licence, LicenceShort, LicenceCreate, LicenceDB from ..services import get_licence, get_licences, add_licence, delete_licence, update_licence router = APIRouter() -@router.get("/", response_model=List[LicenceShort]) +@router.get("/", response_model=List[LicenceShort], status_code=status.HTTP_200_OK) async def licence_list(): return await get_licences() -@router.get('/{id}', response_model=Licence) +@router.get('/{id}', response_model=Licence, status_code=status.HTTP_200_OK) async def licence(id: int): return await get_licence(id) -@router.post("/") +@router.post("/", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) async def create_licence(licence: LicenceCreate): return await add_licence(licence) -@router.delete("/{id}") -async def delete_licence(id: int): +@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_licence_by_id(id: int): return await delete_licence(id) -@router.put("/{id}") -async def update_licence(id: int, item: LicenceCreate): +@router.put("/{id}", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) +async def update_licence_by_id(id: int, item: LicenceCreate): return await update_licence(id, item) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_home.py b/tests/test_home.py new file mode 100644 index 0000000..6c03f52 --- /dev/null +++ b/tests/test_home.py @@ -0,0 +1,12 @@ +import pytest +from httpx import AsyncClient + +from src.app import app + + +@pytest.mark.asyncio +async def test_root(): + async with AsyncClient(app=app, base_url="http://0.0.0.0") as ac: + response = await ac.get("/") + assert response.status_code == 200 + assert response.json() == {'Hello': 'World'} diff --git a/tests/tests_clients/__init__.py b/tests/tests_clients/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_clients/test_add.py b/tests/tests_clients/test_add.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_clients/test_delete.py b/tests/tests_clients/test_delete.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_clients/test_get.py b/tests/tests_clients/test_get.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_clients/test_update.py b/tests/tests_clients/test_update.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_desk/__init__.py b/tests/tests_desk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_desk/test_add.py b/tests/tests_desk/test_add.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_desk/test_delete.py b/tests/tests_desk/test_delete.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_desk/test_get.py b/tests/tests_desk/test_get.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_desk/test_routers.py b/tests/tests_desk/test_routers.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_desk/test_update.py b/tests/tests_desk/test_update.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_reference_book/__init__.py b/tests/tests_reference_book/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_reference_book/test_add.py b/tests/tests_reference_book/test_add.py new file mode 100644 index 0000000..79ed55d --- /dev/null +++ b/tests/tests_reference_book/test_add.py @@ -0,0 +1,109 @@ +from datetime import datetime + +import pytest + +from src.db.db import database +from src.reference_book.models import softwares, licences, modules +from src.reference_book.schemas import SoftwareCreate, LicenceCreate, ModuleCreate +from src.reference_book.services import add_software, add_licence, add_module + + +async def clean_db(table): + query = table.delete() + await database.execute(query) + + +async def init_software(): + software = { + "name": "Windows" + } + query = softwares.insert().values(software) + await database.execute(query) + + +async def init_licence(): + licence = { + "number": 555, + "count_members": 5, + "date_end": datetime.utcnow(), + "client_id": 1, + "software_id": 1 + } + query = licences.insert().values(licence) + await database.execute(query) + + +async def init_module(): + module = { + "name": "Registration", + "software_id": 1 + } + query = modules.insert().values(module) + await database.execute(query) + + +# @pytest.fixture() +# async def prepare_db(table, method): +# await clean_db(table) +# await method() +# yield + + +@pytest.fixture() +async def prepare_db(): + await clean_db(softwares) + await clean_db(licences) + await clean_db(modules) + await init_software() + await init_licence() + await init_module() + yield + + +@pytest.mark.asyncio +@pytest.mark.usefixtures('prepare_db') +# @pytest.mark.usefixtures('prepare_db(softwares, init_software)') +@pytest.mark.parametrize("name", [ + "Adobe Photoshop", "Adobe Illustrator" +]) +async def test_add_software(name: str): + software = SoftwareCreate(name=name) + result = await add_software(software) + assert result is not None + + +@pytest.mark.asyncio +@pytest.mark.usefixtures('prepare_db') +# @pytest.mark.usefixtures('prepare_db(licences, init_licence)') +@pytest.mark.parametrize( + ("number", "count_members", "date_end", "client_id", "software_id"), [ + (-500, 5, datetime.utcnow(), 0, 1), + (545, 5, datetime.utcnow(), 1, 10), + (555, 5, datetime.utcnow(), -1, 10), + ]) +async def test_add_licence( + number: int, + count_members: int, + date_end: datetime, + client_id: int, + software_id: int): + licence = LicenceCreate(number=number, + count_members=count_members, + date_end=date_end, + client_id=client_id, + software_id=software_id) + result = await add_licence(licence) + assert result is not None + + +@pytest.mark.asyncio +@pytest.mark.usefixtures('prepare_db') +# @pytest.mark.usefixtures('prepare_db(modules, init_module)') +@pytest.mark.parametrize(("name", "software_id"), [ + ("authorization", -1), + ("", 10), +]) +async def test_add_module(name: str, software_id: int): + module = ModuleCreate(name=name, software_id=software_id) + result = await add_module(module) + assert result is not None diff --git a/tests/tests_reference_book/test_delete.py b/tests/tests_reference_book/test_delete.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_reference_book/test_get.py b/tests/tests_reference_book/test_get.py new file mode 100644 index 0000000..44c607d --- /dev/null +++ b/tests/tests_reference_book/test_get.py @@ -0,0 +1,99 @@ +from datetime import datetime + +import pytest + +from src.db.db import database +from src.reference_book.models import softwares, licences, modules +from src.reference_book.schemas import SoftwareCreate, LicenceCreate, ModuleCreate +from src.reference_book.services import add_software, add_licence, add_module + + +async def clean_db(table): + query = table.delete() + await database.execute(query) + + +async def init_software(): + software = { + "name": "Windows" + } + query = softwares.insert().values(software) + await database.execute(query) + + +async def init_licence(): + licence = { + "number": 555, + "count_members": 5, + "date_end": datetime.utcnow(), + "client_id": 1, + "software_id": 1 + } + query = licences.insert().values(licence) + await database.execute(query) + + +async def init_module(): + module = { + "name": "Registration", + "software_id": 1 + } + query = modules.insert().values(module) + await database.execute(query) + + +@pytest.fixture() +async def prepare_db(): + await clean_db(softwares) + await clean_db(licences) + await clean_db(modules) + await init_software() + await init_licence() + await init_module() + yield + + +@pytest.mark.asyncio +@pytest.mark.usefixtures('prepare_db') +@pytest.mark.parametrize("name", [ + "Adobe Photoshop", "Adobe Illustrator" +]) +async def test_add_software(name: str): + software = SoftwareCreate(name=name) + result = await add_software(software) + assert result is not None + + +@pytest.mark.asyncio +@pytest.mark.usefixtures('prepare_db') +@pytest.mark.parametrize( + ("number", "count_members", "date_end", "client_id", "software_id"), [ + (-500, 5, datetime.utcnow(), 0, 1), + (545, 5, datetime.utcnow(), 1, 10), + (555, 5, datetime.utcnow(), -1, 10), + ]) +async def test_add_licence( + number: int, + count_members: int, + date_end: datetime, + client_id: int, + software_id: int): + licence = LicenceCreate(number=number, + count_members=count_members, + date_end=date_end, + client_id=client_id, + software_id=software_id) + result = await add_licence(licence) + assert result is not None + + +@pytest.mark.asyncio +@pytest.mark.usefixtures('prepare_db') +@pytest.mark.parametrize(("name", "software_id"), [ + ("authorization", -1), + ("", 10), +]) +async def test_add_module(name: str, software_id: int): + module = ModuleCreate(name=name, software_id=software_id) + result = await add_module(module) + assert result is not None \ No newline at end of file diff --git a/tests/tests_reference_book/test_routes.py b/tests/tests_reference_book/test_routes.py new file mode 100644 index 0000000..adf0b87 --- /dev/null +++ b/tests/tests_reference_book/test_routes.py @@ -0,0 +1,127 @@ +from typing import List + +import pytest +from httpx import AsyncClient + +from src.app import app +from src.reference_book.schemas import ModuleShort, Module, ModuleDB, \ + LicenceShort, SoftwareShort, Software, Licence, LicenceDB + + +@pytest.fixture(autouse=True) +async def client(): + async with AsyncClient( + app=app, + base_url="http://0.0.0.0/reference/") as client: + yield client + + +# TODO сделать отправку данных и сравнения с моделями + +@pytest.mark.asyncio +async def test_get_modules(client): + response = await client.get("/modules/") + assert response.status_code == 200 + assert response.json() is List[ModuleShort] + + +@pytest.mark.asyncio +async def test_get_module(client): + response = await client.get("/modules/1") + assert response.status_code == 200 + assert response.json() is Module + + +@pytest.mark.asyncio +async def test_post_module(client): + response = await client.post("/modules/") + assert response.status_code == 201 + assert response.json() is ModuleDB + + +@pytest.mark.asyncio +async def test_put_module(client): + response = await client.put("/modules/1") + assert response.status_code == 201 + assert response.json() is ModuleDB + + +@pytest.mark.asyncio +async def test_delete_module(client): + response = await client.delete("/modules/1") + assert response.status_code == 204 + + +@pytest.mark.asyncio +async def test_get_licences(client): + response = await client.get("/licences") + assert response.status_code == 200 + assert response.json is List[LicenceShort] + + +@pytest.mark.asyncio +async def test_get_licence(client): + response = await client.get("/licences/1") + assert response.status_code == 200 + assert response.json() is Licence + + +@pytest.mark.asyncio +async def test_post_licence(client): + response = await client.post("/licences/") + assert response.status_code == 201 + assert response.json() is LicenceDB + + +@pytest.mark.asyncio +async def test_put_licence(client): + response = await client.put("/licences/1") + assert response.status_code == 201 + assert response.json() is LicenceDB + + +@pytest.mark.asyncio +async def test_delete_licence(client): + response = await client.delete("/licences/1") + assert response.status_code == 204 + + +@pytest.mark.asyncio +async def test_get_softwares(client): + response = await client.get("/software") + assert response.status_code == 200 + assert response.json() is List[SoftwareShort] + + +@pytest.mark.asyncio +async def test_get_software(client): + response = await client.get("/software/1") + assert response.status_code == 200 + assert response.json() is SoftwareShort + + +@pytest.mark.asyncio +async def test_get_software_with_modules(client): + response = await client.get("/software/1/modules") + assert response.status_code == 200 + assert response.json() is Software + + +@pytest.mark.asyncio +async def test_post_software(client): + response = await client.post("/software/") + assert response.status_code == 201 + assert response.json() is SoftwareShort + + +@pytest.mark.asyncio +async def test_put_software(client): + response = await client.put("/software/1") + assert response.status_code == 201 + assert response.json() is SoftwareShort + + +@pytest.mark.asyncio +async def test_delete_software(client): + response = await client.delete("/software/1") + assert response.status_code == 204 diff --git a/tests/tests_reference_book/test_update.py b/tests/tests_reference_book/test_update.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_users/__init__.py b/tests/tests_users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_users/test_add.py b/tests/tests_users/test_add.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_users/test_delete.py b/tests/tests_users/test_delete.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_users/test_get.py b/tests/tests_users/test_get.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests_users/test_update.py b/tests/tests_users/test_update.py new file mode 100644 index 0000000..e69de29 From 62f6cef46a857ced33432ebe322a5cde6130672f Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sun, 25 Apr 2021 21:11:01 +0500 Subject: [PATCH 11/55] add status code --- src/reference_book/api/module.py | 18 +++++++++--------- src/reference_book/api/software.py | 21 +++++++++++---------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/reference_book/api/module.py b/src/reference_book/api/module.py index 406f94d..e69893e 100644 --- a/src/reference_book/api/module.py +++ b/src/reference_book/api/module.py @@ -1,32 +1,32 @@ from typing import List -from fastapi import APIRouter -from ..schemas import Module, ModuleShort, ModuleCreate +from fastapi import APIRouter, status +from ..schemas import Module, ModuleShort, ModuleCreate, ModuleDB from ..services import get_module, get_modules, add_module, delete_module, update_module router = APIRouter() -@router.get("/", response_model=List[ModuleShort]) +@router.get("/", response_model=List[ModuleShort], status_code=status.HTTP_200_OK) async def module_list(): return await get_modules() -@router.get('/{id}', response_model=Module) +@router.get('/{id}', response_model=Module, status_code=status.HTTP_200_OK) async def module(id: int): return await get_module(id) -@router.post("/") +@router.post("/", response_model=ModuleDB, status_code=status.HTTP_201_CREATED) async def create_module(item: ModuleCreate): return await add_module(item) -@router.delete("/{id}") -async def delete_module(id: int): +@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_module_by_id(id: int): return await delete_module(id) -@router.put("/{id}") -async def update_module(id: int, item: ModuleCreate): +@router.put("/{id}", response_model=ModuleDB, status_code=status.HTTP_201_CREATED) +async def update_module_by_id(id: int, item: ModuleCreate): return await update_module(id, item) diff --git a/src/reference_book/api/software.py b/src/reference_book/api/software.py index acd73ea..e70c9f1 100644 --- a/src/reference_book/api/software.py +++ b/src/reference_book/api/software.py @@ -1,6 +1,7 @@ from typing import List -from fastapi import APIRouter +from fastapi import APIRouter, status + from ..services import get_software, get_softwares, get_software_with_modules, \ add_software, delete_software, update_software from ..schemas import Software, SoftwareShort, SoftwareCreate @@ -8,31 +9,31 @@ router = APIRouter() -@router.get('/', response_model=List[SoftwareShort]) +@router.get('/', response_model=List[SoftwareShort], status_code=status.HTTP_200_OK) async def software_list(): return await get_softwares() -@router.get('/{id}', response_model=SoftwareShort) +@router.get('/{id}', response_model=SoftwareShort, status_code=status.HTTP_200_OK) async def software(id: int): return await get_software(id) -@router.get("/{id}/modules", response_model=Software) +@router.get("/{id}/modules", response_model=Software, status_code=status.HTTP_200_OK) async def get_software_modules(id: int): return await get_software_with_modules(id) -@router.post("/") -async def create_licence(software: SoftwareCreate): +@router.post("/", response_model=SoftwareShort, status_code=status.HTTP_201_CREATED) +async def create_software(software: SoftwareCreate): return await add_software(software) -@router.delete("/{id}") -async def delete_software(id: int): +@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_software_by_id(id: int): return await delete_software(id) -@router.put("/{id}") -async def update_software(id: int, item: SoftwareCreate): +@router.put("/{id}", response_model=SoftwareShort, status_code=status.HTTP_201_CREATED) +async def update_software_by_id(id: int, item: SoftwareCreate): return await update_software(id, item) From 1b8b8c3c3bd9e35941bdc259fd61354dd609a10b Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sun, 25 Apr 2021 21:11:53 +0500 Subject: [PATCH 12/55] add crud methods --- src/reference_book/schemas.py | 11 +++++++++++ src/reference_book/services.py | 6 ++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/reference_book/schemas.py b/src/reference_book/schemas.py index db8c972..439d317 100644 --- a/src/reference_book/schemas.py +++ b/src/reference_book/schemas.py @@ -41,6 +41,12 @@ class Config: orm_mode = True +class LicenceDB(LicenceBase): + id: int + client_id: int + software_id: int + + class Licence(LicenceBase): id: int client_id: ClientShort @@ -58,6 +64,11 @@ class ModuleCreate(ModuleBase): software_id: int +class ModuleDB(ModuleBase): + id: int + software_id: int + + class Module(ModuleBase): id: int software: SoftwareShort diff --git a/src/reference_book/services.py b/src/reference_book/services.py index 49b7151..c15ad93 100644 --- a/src/reference_book/services.py +++ b/src/reference_book/services.py @@ -79,7 +79,8 @@ async def get_licence(id: int): async def update_module(id: int, module: ModuleCreate): query = modules.update().where(modules.c.id == id).values(**module.dict()) - return await database.execute(query) + id_result = await database.execute(query) + return id_result async def update_licence(id: int, licence: LicenceCreate): @@ -94,7 +95,8 @@ async def update_software(id: int, software: SoftwareCreate): async def delete_module(id: int): query = modules.delete().where(modules.c.id == id) - return await database.execute(query) + result = await database.execute(query) + return result async def delete_licence(id: int): From a6899b6d137452e0cefb6d0d005785e45655988d Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sun, 25 Apr 2021 21:12:27 +0500 Subject: [PATCH 13/55] add crud methods for desk --- src/desk/models.py | 4 ++-- src/desk/routes.py | 50 +++++++++++++++++++++++++++++++++++++------- src/desk/schemas.py | 24 +++++++++++++++++---- src/desk/services.py | 37 ++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 14 deletions(-) diff --git a/src/desk/models.py b/src/desk/models.py index 59f81e6..261011f 100644 --- a/src/desk/models.py +++ b/src/desk/models.py @@ -19,11 +19,11 @@ class Appeal(Base): id = Column(Integer, primary_key=True, index=True, unique=True) topic = Column(String(100), nullable=False) text = Column(String(500), nullable=False) - author = Column(String, ForeignKey('employee.id')) + author_id = Column(String, ForeignKey('employee.id')) date_create = Column(DateTime(timezone=True), server_default=sql.func.now(), nullable=False) date_processing = Column(DateTime, default=None) status = Column(Enum(StatusTasks), default=StatusTasks.new, nullable=False) - responsible = Column(String, ForeignKey('developer.id'), nullable=False) + responsible_id = Column(String, ForeignKey('developer.id'), nullable=False) software_id = Column(Integer, ForeignKey('software.id'), nullable=False) module_id = Column(Integer, ForeignKey('module.id'), nullable=False) # attachments diff --git a/src/desk/routes.py b/src/desk/routes.py index ed46e4c..cf29f93 100644 --- a/src/desk/routes.py +++ b/src/desk/routes.py @@ -1,26 +1,60 @@ -from fastapi import APIRouter +from fastapi import APIRouter, status from typing import List -from .services import get_appeals, get_appeal, get_comments, get_comment -from .schemas import AppealShort, Appeal, CommentShort, Comment, AppealBase +from .services import get_appeals, get_appeal, get_comments, get_comment, \ + add_appeal, add_comment, update_appeal, update_comment, delete_appeal, delete_comment +from .schemas import AppealShort, CommentShort, Comment, AppealBase, AppealCreate, CommentCreate, CommentDB, \ + AppealUpdate +from ..users.models import EmployeeTable router = APIRouter() -@router.get("/", response_model=List[AppealBase]) -async def appeals_list(): +@router.get("/", response_model=List[AppealBase], status_code=status.HTTP_200_OK) +async def appeals_list(user: Employee = Depends(fastapi_users.get_current_active_user)): return await get_appeals() -@router.get("/{id}", response_model=AppealShort) +@router.get("/{id}", response_model=AppealShort, status_code=status.HTTP_200_OK) # TODO change to Appeal async def appeal(id: int): return await get_appeal(id) -@router.get("/{id}/comments", response_model=List[CommentShort]) +@router.post("/", response_model=AppealShort, status_code=status.HTTP_201_CREATED) +async def create_appeal(item: AppealCreate): + return await add_appeal(item) + + +@router.put("/{id}", response_model=AppealShort, status_code=status.HTTP_201_CREATED) +async def update_appeal_by_id(id: int, item: AppealUpdate): + return await update_appeal(id, item) + + +@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_appeal_by_id(id: int): + return await delete_appeal(id) + + +@router.get("/{id}/comments", response_model=List[CommentShort], status_code=status.HTTP_200_OK) async def comments_list(id: int): return await get_comments(id) -@router.get("/{id}/comments/{pk}", response_model=Comment) +@router.get("/{id}/comments/{pk}", response_model=Comment, status_code=status.HTTP_200_OK) async def comment(id: int, pk: int): return await get_comment(id, pk) + + +@router.post("/{id}/comments/", response_model=CommentDB, status_code=status.HTTP_201_CREATED) +async def create_comment(id: int, item: CommentCreate): + return await add_comment(id, item) + + +@router.put("/{id}/comments/{pk}", response_model=CommentDB, status_code=status.HTTP_201_CREATED) +async def update_comment_by_id(id: int, item: CommentCreate): + return await update_comment(id, item) + + +@router.delete("/{id}/comments/{pk}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_comment_by_id(id: int): + return await delete_comment(id) + diff --git a/src/desk/schemas.py b/src/desk/schemas.py index a92b22b..de17d8c 100644 --- a/src/desk/schemas.py +++ b/src/desk/schemas.py @@ -13,17 +13,27 @@ class AppealBase(BaseModel): class AppealCreate(AppealBase): author_id: str - responsible_id: str + responsible_id: Optional[str] software_id: int module_id: int +class AppealUpdate(AppealCreate): + date_processing: Optional[datetime] = None + + +class AppealDB(AppealCreate): # TODO проверить нужен ли вообще + id: int + date_create: datetime + date_processing: Optional[datetime] + + class AppealShort(AppealBase): id: int - author: int # TODO change to Member + author_id: str # TODO change to Member date_create: datetime date_processing: Optional[datetime] = None - responsible: int # TODO change to Developer + responsible_id: str # TODO change to Developer software: SoftwareShort module: ModuleShort @@ -33,7 +43,6 @@ class CommentBase(BaseModel): class CommentCreate(CommentBase): - appeal_id: int author_id: int @@ -44,6 +53,13 @@ class Comment(CommentBase): author: int # TODO maybe change to member (Union[member, developer]) +class CommentDB(CommentBase): + id: int + date_create: datetime + appeal_id: int + author_id: int + + class CommentShort(CommentBase): id: int date_create: datetime diff --git a/src/desk/services.py b/src/desk/services.py index db140c2..97c9df3 100644 --- a/src/desk/services.py +++ b/src/desk/services.py @@ -1,3 +1,4 @@ +from .schemas import AppealCreate, CommentCreate, AppealUpdate from ..db.db import database from .models import appeals, comments from ..reference_book.models import softwares, modules @@ -34,3 +35,39 @@ async def get_comment(id: int, pk: int): if result: return dict(result) return None + + +async def add_appeal(appeal: AppealCreate): + query = appeals.insert().values(**appeal.dict()) + id = await database.execute(query) + return await get_appeal(id) + + +async def add_comment(appeal_id: int, comment: CommentCreate): + query = comments.insert().values({**comment.dict(), "appeal_id": appeal_id}) + comment_id = await database.execute(query) + return {**comment.dict(), "appeal_id": appeal_id, "id": comment_id} + + +async def update_appeal(id: int, appeal: AppealUpdate): + query = appeals.update().where(appeals.c.id == id).values(**appeal.dict()) + id_result = await database.execute(query) + return await get_appeal(id_result) + + +async def update_comment(id: int, comment: CommentCreate): + query = comments.update().where(comments.c.id == id).values(**comment.dict()) + result = await database.execute(query) + return result + + +async def delete_appeal(id: int): + query = appeals.delete().where(appeals.c.id == id) + result = await database.execute(query) + return result + + +async def delete_comment(id: int): + query = comments.delete().where(comments.c.id == id) + result = await database.execute(query) + return result From c99f9fc83e02266473c93063b957daae7c3ebcd7 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sun, 25 Apr 2021 23:36:56 +0500 Subject: [PATCH 14/55] =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D0=B5=D0=B9,=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D1=8C=20=D1=8D=D1=82=D0=BE=20=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=BA?= =?UTF-8?q?=D0=BB=D0=B0=D1=81=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/users/logic.py | 37 ++++++++++++------------------------ src/users/models.py | 25 +++++++----------------- src/users/routes.py | 45 +++++++------------------------------------- src/users/schemas.py | 35 ++++------------------------------ 4 files changed, 30 insertions(+), 112 deletions(-) diff --git a/src/users/logic.py b/src/users/logic.py index 8f23146..aaac6ad 100644 --- a/src/users/logic.py +++ b/src/users/logic.py @@ -1,13 +1,10 @@ -from typing import Union - from fastapi_users.authentication import JWTAuthentication from fastapi_users import FastAPIUsers from requests import Request from ..config import SECRET -from .schemas import Employee, EmployeeCreate, EmployeeUpdate, EmployeeDB, \ - Developer, DeveloperCreate, DeveloperUpdate, DeveloperDB -from .models import employee_db, developer_db +from .schemas import User, UserCreate, UserUpdate, UserDB +from .models import user_db auth_backends = [] @@ -16,37 +13,27 @@ auth_backends.append(jwt_authentication) -employee_users = FastAPIUsers( - employee_db, - auth_backends, - Employee, - EmployeeCreate, - EmployeeUpdate, - EmployeeDB, -) - - -developer_users = FastAPIUsers( - developer_db, +all_users = FastAPIUsers( + user_db, auth_backends, - Developer, - DeveloperCreate, - DeveloperUpdate, - DeveloperDB, + User, + UserCreate, + UserUpdate, + UserDB, ) -async def on_after_forgot_password(user: Union[EmployeeDB, DeveloperDB], token: str, request: Request): +async def on_after_forgot_password(user: UserDB, token: str, request: Request): print(f"User {user.id} has forgot their password. Reset token: {token}") -async def on_after_reset_password(user: Union[EmployeeDB, DeveloperDB], request: Request): +async def on_after_reset_password(user: UserDB, request: Request): print(f"User {user.id} has reset their password.") -async def after_verification_request(user: Union[EmployeeDB, DeveloperDB], token: str, request: Request): +async def after_verification_request(user: UserDB, token: str, request: Request): print(f"Verification requested for user {user.id}. Verification token: {token}") -async def after_verification(user: Union[EmployeeDB, DeveloperDB], request: Request): +async def after_verification(user: UserDB, request: Request): print(f"{user.id} is now verified.") diff --git a/src/users/models.py b/src/users/models.py index ebca9e7..2d583fd 100644 --- a/src/users/models.py +++ b/src/users/models.py @@ -1,33 +1,22 @@ from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase from sqlalchemy import Column, Integer, Boolean, ForeignKey, DateTime, sql, String -from .schemas import EmployeeDB, DeveloperDB +from .schemas import UserDB from ..db.db import database, Base -class EmployeeTable(Base, SQLAlchemyBaseUserTable): - __tablename__ = 'employee' +class UserTable(Base, SQLAlchemyBaseUserTable): + __tablename__ = 'user' name = Column(String, nullable=False) surname = Column(String, nullable=False) patronymic = Column(String, nullable=True) + # avatar is_owner = Column(Boolean, default=False, nullable=False) - client = Column(Integer, ForeignKey('client.id')) # TODO сделать проверку на существующего владельца клиента + client_id = Column(Integer, ForeignKey('client.id')) # TODO сделать проверку на существующего владельца клиента date_reg = Column(DateTime(timezone=True), server_default=sql.func.now()) date_block = Column(DateTime, default=None, nullable=True) - # avatar - - -class DeveloperTable(Base, SQLAlchemyBaseUserTable): - __tablename__ = 'developer' - - name = Column(String, nullable=False) - surname = Column(String, nullable=False) - patronymic = Column(String, nullable=True) - # avatar -employees = EmployeeTable.__table__ -employee_db = SQLAlchemyUserDatabase(EmployeeDB, database, employees) -developers = DeveloperTable.__table__ -developer_db = SQLAlchemyUserDatabase(DeveloperDB, database, developers) +users = UserTable.__table__ +user_db = SQLAlchemyUserDatabase(UserDB, database, users) diff --git a/src/users/routes.py b/src/users/routes.py index 3de4d92..48f570e 100644 --- a/src/users/routes.py +++ b/src/users/routes.py @@ -2,71 +2,40 @@ from fastapi import APIRouter from ..config import SECRET -from src.users.logic import jwt_authentication, employee_users, developer_users, \ +from src.users.logic import jwt_authentication, all_users, \ on_after_forgot_password, on_after_reset_password, after_verification, after_verification_request router = APIRouter() @router.post("/auth/jwt/refresh") -async def refresh_jwt(response: Response, user=Depends(employee_users.get_current_active_user)): +async def refresh_jwt(response: Response, user=Depends(all_users.get_current_active_user)): return await jwt_authentication.get_login_response(user, response) router.include_router( - employee_users.get_auth_router(jwt_authentication), + all_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"]) router.include_router( - employee_users.get_register_router(), + all_users.get_register_router(), prefix="/auth", tags=["auth"]) router.include_router( - employee_users.get_reset_password_router( + all_users.get_reset_password_router( SECRET, after_forgot_password=on_after_forgot_password, after_reset_password=on_after_reset_password), prefix="/auth", tags=["auth"]) router.include_router( - employee_users.get_users_router(), + all_users.get_users_router(), prefix="/users", tags=["users"]) router.include_router( - employee_users.get_verify_router( + all_users.get_verify_router( SECRET, after_verification_request=after_verification_request, after_verification=after_verification), prefix="/auth", tags=["auth"]) - - -router.include_router( - developer_users.get_auth_router(jwt_authentication), - prefix="/developers/auth/jwt", - tags=["dev_auth"]) -router.include_router( - developer_users.get_register_router(), - prefix="/developers/auth", - tags=["dev_auth"]) -router.include_router( - developer_users.get_reset_password_router( - SECRET, - after_forgot_password=on_after_forgot_password, - after_reset_password=on_after_reset_password), - prefix="/developers/auth", - tags=["dev_auth"]) -router.include_router( - developer_users.get_users_router(), - prefix="/developers", - tags=["developers"]) -router.include_router( - developer_users.get_verify_router( - SECRET, - after_verification_request=after_verification_request, - after_verification=after_verification), - prefix="/developers/auth", - tags=["dev_auth"]) - - -# router.include_router(employee_users.router, prefix="/users", tags=["test_user"]) diff --git a/src/users/schemas.py b/src/users/schemas.py index b337bf9..ca1859e 100644 --- a/src/users/schemas.py +++ b/src/users/schemas.py @@ -5,7 +5,7 @@ from typing import Optional -class Employee(models.BaseUser): +class User(models.BaseUser): name: str surname: str patronymic: Optional[str] @@ -14,7 +14,7 @@ class Employee(models.BaseUser): date_block: Optional[datetime] -class EmployeeCreate(models.BaseUserCreate): +class UserCreate(models.BaseUserCreate): name: str surname: str patronymic: Optional[str] @@ -30,36 +30,9 @@ def valid_password(cls, v: str): return v -class EmployeeUpdate(Employee, models.BaseUserUpdate): +class UserUpdate(User, models.BaseUserUpdate): pass -class EmployeeDB(Employee, models.BaseUserDB): +class UserDB(User, models.BaseUserDB): date_reg: datetime - - -class Developer(models.BaseUser): - name: str - surname: str - patronymic: Optional[str] - is_superuser: Optional[bool] = True - - -class DeveloperCreate(models.BaseUserCreate): - name: str - surname: str - patronymic: Optional[str] - - @validator('password') - def valid_password(cls, v: str): - if len(v) < 6: - raise ValueError('Password should be at least 6 characters') - return v - - -class DeveloperUpdate(Developer, models.BaseUserUpdate): - pass - - -class DeveloperDB(Developer, models.BaseUserDB): - pass From 490981d38bae81ffb5b6f1eea3b4e2976b811dc9 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Mon, 26 Apr 2021 00:01:20 +0500 Subject: [PATCH 15/55] fix changed users --- src/client_account/schemas.py | 4 ++-- src/client_account/services.py | 4 ++-- src/users/schemas.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client_account/schemas.py b/src/client_account/schemas.py index 896eec8..39be2d5 100644 --- a/src/client_account/schemas.py +++ b/src/client_account/schemas.py @@ -1,7 +1,7 @@ from datetime import datetime from pydantic import BaseModel -from ..users.schemas import Employee +from ..users.schemas import User class ClientBase(BaseModel): @@ -17,7 +17,7 @@ class Client(ClientBase): id: int is_active: bool date_create: datetime - owner: Employee + owner: User class ClientShort(ClientBase): diff --git a/src/client_account/services.py b/src/client_account/services.py index 8a91a24..6606496 100644 --- a/src/client_account/services.py +++ b/src/client_account/services.py @@ -1,6 +1,6 @@ from ..db.db import database from .models import clients -from ..users.models import employees +from ..users.models import users async def get_clients(): @@ -13,6 +13,6 @@ async def get_client(id: int): result = await database.fetch_one(query=query) if result: result = dict(result) - query = employees.select().where(employees.c.id == result["owner_id"]) + query = users.select().where(users.c.id == result["owner_id"]) owner = await database.fetch_one(query=query) return {**result, "owner": owner} diff --git a/src/users/schemas.py b/src/users/schemas.py index ca1859e..d7a0033 100644 --- a/src/users/schemas.py +++ b/src/users/schemas.py @@ -10,7 +10,7 @@ class User(models.BaseUser): surname: str patronymic: Optional[str] is_owner: bool - client: int + client_id: int date_block: Optional[datetime] @@ -19,7 +19,7 @@ class UserCreate(models.BaseUserCreate): surname: str patronymic: Optional[str] is_owner: bool = False - client: int + client_id: int date_reg: datetime # TODO попробовать удалить данное поле из регистрации. Пока без нее не работет date_block: Optional[datetime] From c8640016335d6029a5252f9cce01a0dd5290bddd Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Mon, 26 Apr 2021 00:01:40 +0500 Subject: [PATCH 16/55] add check role --- src/desk/routes.py | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/desk/routes.py b/src/desk/routes.py index cf29f93..cae9f0d 100644 --- a/src/desk/routes.py +++ b/src/desk/routes.py @@ -1,60 +1,74 @@ -from fastapi import APIRouter, status -from typing import List +from fastapi import APIRouter, status, Depends, HTTPException +from typing import List, Optional + +from fastapi_users.models import BaseUserDB + from .services import get_appeals, get_appeal, get_comments, get_comment, \ add_appeal, add_comment, update_appeal, update_comment, delete_appeal, delete_comment from .schemas import AppealShort, CommentShort, Comment, AppealBase, AppealCreate, CommentCreate, CommentDB, \ AppealUpdate -from ..users.models import EmployeeTable +from ..users.models import UserTable +from ..users.logic import all_users router = APIRouter() +any_user = all_users.current_user(active=True) +developer_user = all_users.current_user(active=True, superuser=True) + + +async def get_owner(user: UserTable = Depends(any_user)): + if not user.is_owner: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) + return user + + @router.get("/", response_model=List[AppealBase], status_code=status.HTTP_200_OK) -async def appeals_list(user: Employee = Depends(fastapi_users.get_current_active_user)): +async def appeals_list(user: UserTable = Depends(get_owner)): return await get_appeals() @router.get("/{id}", response_model=AppealShort, status_code=status.HTTP_200_OK) # TODO change to Appeal -async def appeal(id: int): +async def appeal(id: int, user: UserTable = Depends(any_user)): return await get_appeal(id) @router.post("/", response_model=AppealShort, status_code=status.HTTP_201_CREATED) -async def create_appeal(item: AppealCreate): +async def create_appeal(item: AppealCreate, user: UserTable = Depends(any_user)): return await add_appeal(item) @router.put("/{id}", response_model=AppealShort, status_code=status.HTTP_201_CREATED) -async def update_appeal_by_id(id: int, item: AppealUpdate): +async def update_appeal_by_id(id: int, item: AppealUpdate, user: UserTable = Depends(any_user)): return await update_appeal(id, item) @router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_appeal_by_id(id: int): +async def delete_appeal_by_id(id: int, user: UserTable = Depends(any_user)): return await delete_appeal(id) @router.get("/{id}/comments", response_model=List[CommentShort], status_code=status.HTTP_200_OK) -async def comments_list(id: int): +async def comments_list(id: int, user: UserTable = Depends(any_user)): return await get_comments(id) @router.get("/{id}/comments/{pk}", response_model=Comment, status_code=status.HTTP_200_OK) -async def comment(id: int, pk: int): +async def comment(id: int, pk: int, user: UserTable = Depends(any_user)): return await get_comment(id, pk) @router.post("/{id}/comments/", response_model=CommentDB, status_code=status.HTTP_201_CREATED) -async def create_comment(id: int, item: CommentCreate): +async def create_comment(id: int, item: CommentCreate, user: UserTable = Depends(any_user)): return await add_comment(id, item) @router.put("/{id}/comments/{pk}", response_model=CommentDB, status_code=status.HTTP_201_CREATED) -async def update_comment_by_id(id: int, item: CommentCreate): +async def update_comment_by_id(id: int, item: CommentCreate, user: UserTable = Depends(any_user)): return await update_comment(id, item) @router.delete("/{id}/comments/{pk}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_comment_by_id(id: int): +async def delete_comment_by_id(id: int, user: UserTable = Depends(any_user)): return await delete_comment(id) From ef0f8bd7de4ed8e7572e07799078f7c4f095e232 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Mon, 26 Apr 2021 19:24:03 +0500 Subject: [PATCH 17/55] =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=BA=D0=BE=D1=80?= =?UTF-8?q?=D1=80=D0=B5=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=81=D1=85=D0=B5=D0=BC=D1=8B=20=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/reference_book/api/software.py | 10 +++++----- src/reference_book/schemas.py | 12 ++++++------ src/reference_book/services.py | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/reference_book/api/software.py b/src/reference_book/api/software.py index e70c9f1..0fbc15a 100644 --- a/src/reference_book/api/software.py +++ b/src/reference_book/api/software.py @@ -4,17 +4,17 @@ from ..services import get_software, get_softwares, get_software_with_modules, \ add_software, delete_software, update_software -from ..schemas import Software, SoftwareShort, SoftwareCreate +from ..schemas import Software, SoftwareDB, SoftwareCreate router = APIRouter() -@router.get('/', response_model=List[SoftwareShort], status_code=status.HTTP_200_OK) +@router.get('/', response_model=List[SoftwareDB], status_code=status.HTTP_200_OK) async def software_list(): return await get_softwares() -@router.get('/{id}', response_model=SoftwareShort, status_code=status.HTTP_200_OK) +@router.get('/{id}', response_model=SoftwareDB, status_code=status.HTTP_200_OK) async def software(id: int): return await get_software(id) @@ -24,7 +24,7 @@ async def get_software_modules(id: int): return await get_software_with_modules(id) -@router.post("/", response_model=SoftwareShort, status_code=status.HTTP_201_CREATED) +@router.post("/", response_model=SoftwareDB, status_code=status.HTTP_201_CREATED) async def create_software(software: SoftwareCreate): return await add_software(software) @@ -34,6 +34,6 @@ async def delete_software_by_id(id: int): return await delete_software(id) -@router.put("/{id}", response_model=SoftwareShort, status_code=status.HTTP_201_CREATED) +@router.put("/{id}", response_model=SoftwareDB, status_code=status.HTTP_201_CREATED) async def update_software_by_id(id: int, item: SoftwareCreate): return await update_software(id, item) diff --git a/src/reference_book/schemas.py b/src/reference_book/schemas.py index 439d317..366c414 100644 --- a/src/reference_book/schemas.py +++ b/src/reference_book/schemas.py @@ -1,9 +1,9 @@ from datetime import datetime -from typing import List, Optional +from typing import List from pydantic import BaseModel -from src.client_account.schemas import ClientShort +# from src.client_account.schemas import ClientDB class SoftwareBase(BaseModel): @@ -14,7 +14,7 @@ class SoftwareCreate(SoftwareBase): pass -class SoftwareShort(SoftwareBase): +class SoftwareDB(SoftwareBase): id: int class Config: @@ -49,8 +49,8 @@ class LicenceDB(LicenceBase): class Licence(LicenceBase): id: int - client_id: ClientShort - software: SoftwareShort + client_id: int + software: SoftwareDB class Config: orm_mode = True @@ -71,7 +71,7 @@ class ModuleDB(ModuleBase): class Module(ModuleBase): id: int - software: SoftwareShort + software: SoftwareDB class Config: orm_mode = True diff --git a/src/reference_book/services.py b/src/reference_book/services.py index c15ad93..eb7180f 100644 --- a/src/reference_book/services.py +++ b/src/reference_book/services.py @@ -71,9 +71,9 @@ async def get_licence(id: int): result = await database.fetch_one(query=licences.select().where(licences.c.id == id)) if result is not None: licence = dict(result) - client = await get_client(licence["client_id"]) + # client = await get_client(licence["client_id"]) software = await get_software(licence["software_id"]) - return {**licence, "client": client, "software": software} + return {**licence, "software": software} return None From 108b254ae6d6dcc1450b368f26564293957cf857 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Mon, 26 Apr 2021 19:25:20 +0500 Subject: [PATCH 18/55] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=B9=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D0=BD=D0=B8=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=20=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/users/logic.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/users/logic.py b/src/users/logic.py index aaac6ad..acf8baf 100644 --- a/src/users/logic.py +++ b/src/users/logic.py @@ -1,10 +1,11 @@ +from fastapi import HTTPException, Depends, status from fastapi_users.authentication import JWTAuthentication from fastapi_users import FastAPIUsers from requests import Request from ..config import SECRET from .schemas import User, UserCreate, UserUpdate, UserDB -from .models import user_db +from .models import user_db, UserTable auth_backends = [] @@ -12,7 +13,6 @@ auth_backends.append(jwt_authentication) - all_users = FastAPIUsers( user_db, auth_backends, @@ -22,6 +22,34 @@ UserDB, ) +any_user = all_users.current_user(active=True) +employee = all_users.current_user(active=True, superuser=False) +developer_user = all_users.current_user(active=True, superuser=True) + + +async def get_owner(user: UserTable = Depends(any_user)): + if not user.is_owner: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) + return user + + +async def get_owner_with_superuser(user: UserTable = Depends(any_user)): + if not user.is_superuser and not user.is_owner: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) + return user + + +async def get_client_users(client_id: int, user: UserTable = Depends(any_user)): + if user.client_id != client_id: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) + return user + + +async def get_client_users_with_superuser(client_id: int, user: UserTable = Depends(any_user)): + if not user.is_superuser and user.client_id != client_id: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) + return user + async def on_after_forgot_password(user: UserDB, token: str, request: Request): print(f"User {user.id} has forgot their password. Reset token: {token}") From 2eadb6f52dea7cced6d86ec0a0f3e79879f7b2c7 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Mon, 26 Apr 2021 19:26:42 +0500 Subject: [PATCH 19/55] =?UTF-8?q?=D0=9D=D0=B0=D0=BF=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=D0=BB=20CRUD=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D1=8B=20=D0=B8?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=B0=20=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=B0?= =?UTF-8?q?=20=D0=BA=20=D0=B4=D0=BE=D1=81=D0=BA=D0=B5=20=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/desk/models.py | 15 +++--- src/desk/routes.py | 73 ++++++++++---------------- src/desk/schemas.py | 57 +++++++++++--------- src/desk/services.py | 121 ++++++++++++++++++++++++++++--------------- 4 files changed, 147 insertions(+), 119 deletions(-) diff --git a/src/desk/models.py b/src/desk/models.py index 261011f..31e6f0f 100644 --- a/src/desk/models.py +++ b/src/desk/models.py @@ -19,25 +19,24 @@ class Appeal(Base): id = Column(Integer, primary_key=True, index=True, unique=True) topic = Column(String(100), nullable=False) text = Column(String(500), nullable=False) - author_id = Column(String, ForeignKey('employee.id')) - date_create = Column(DateTime(timezone=True), server_default=sql.func.now(), nullable=False) + client_id = Column(Integer, ForeignKey('client.id')) + author_id = Column(String, ForeignKey('user.id')) + date_create = Column(DateTime(timezone=True), server_default=sql.func.now()) date_processing = Column(DateTime, default=None) - status = Column(Enum(StatusTasks), default=StatusTasks.new, nullable=False) - responsible_id = Column(String, ForeignKey('developer.id'), nullable=False) + status = Column(Enum(StatusTasks), default=StatusTasks.new) + responsible_id = Column(String, ForeignKey('user.id'), nullable=True) software_id = Column(Integer, ForeignKey('software.id'), nullable=False) module_id = Column(Integer, ForeignKey('module.id'), nullable=False) # attachments - # comments = relationship('Comment') - class Comment(Base): __tablename__ = 'comment' id = Column(Integer, primary_key=True, index=True, unique=True) text = Column(String(300), nullable=False) - appeal = Column(Integer, ForeignKey('appeal.id'), nullable=False) - author = Column(String, ForeignKey('employee.id'), nullable=False) + appeal_id = Column(Integer, ForeignKey('appeal.id'), nullable=False) + author_id = Column(String, ForeignKey('user.id'), nullable=False) date_create = Column(DateTime(timezone=True), server_default=sql.func.now()) diff --git a/src/desk/routes.py b/src/desk/routes.py index cae9f0d..618f1fd 100644 --- a/src/desk/routes.py +++ b/src/desk/routes.py @@ -1,74 +1,57 @@ -from fastapi import APIRouter, status, Depends, HTTPException -from typing import List, Optional - -from fastapi_users.models import BaseUserDB +from fastapi import APIRouter, status, Depends +from typing import List from .services import get_appeals, get_appeal, get_comments, get_comment, \ - add_appeal, add_comment, update_appeal, update_comment, delete_appeal, delete_comment -from .schemas import AppealShort, CommentShort, Comment, AppealBase, AppealCreate, CommentCreate, CommentDB, \ - AppealUpdate + add_appeal, add_comment, update_appeal, update_comment, delete_comment +from .schemas import Appeal, CommentShort, Comment, AppealCreate, CommentCreate, CommentDB, \ + AppealUpdate, AppealDB, AppealShort, CommentUpdate from ..users.models import UserTable -from ..users.logic import all_users +from ..users.logic import employee router = APIRouter() -any_user = all_users.current_user(active=True) -developer_user = all_users.current_user(active=True, superuser=True) - - -async def get_owner(user: UserTable = Depends(any_user)): - if not user.is_owner: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) - return user - - -@router.get("/", response_model=List[AppealBase], status_code=status.HTTP_200_OK) -async def appeals_list(user: UserTable = Depends(get_owner)): - return await get_appeals() - - -@router.get("/{id}", response_model=AppealShort, status_code=status.HTTP_200_OK) # TODO change to Appeal -async def appeal(id: int, user: UserTable = Depends(any_user)): - return await get_appeal(id) +@router.get("/", response_model=List[AppealShort], status_code=status.HTTP_200_OK) +async def appeals_list(user: UserTable = Depends(employee)): + return await get_appeals(user) -@router.post("/", response_model=AppealShort, status_code=status.HTTP_201_CREATED) -async def create_appeal(item: AppealCreate, user: UserTable = Depends(any_user)): - return await add_appeal(item) +@router.get("/{id}", response_model=Appeal, status_code=status.HTTP_200_OK) +async def appeal(id: int, user: UserTable = Depends(employee)): + return await get_appeal(id, user) -@router.put("/{id}", response_model=AppealShort, status_code=status.HTTP_201_CREATED) -async def update_appeal_by_id(id: int, item: AppealUpdate, user: UserTable = Depends(any_user)): - return await update_appeal(id, item) +@router.post("/", response_model=AppealDB, status_code=status.HTTP_201_CREATED) +async def create_appeal(item: AppealCreate, user: UserTable = Depends(employee)): + return await add_appeal(item, user) -@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_appeal_by_id(id: int, user: UserTable = Depends(any_user)): - return await delete_appeal(id) +@router.put("/{id}", response_model=AppealDB, status_code=status.HTTP_201_CREATED) +async def update_appeal_by_id(id: int, item: AppealUpdate, user: UserTable = Depends(employee)): + return await update_appeal(id, item, user) @router.get("/{id}/comments", response_model=List[CommentShort], status_code=status.HTTP_200_OK) -async def comments_list(id: int, user: UserTable = Depends(any_user)): - return await get_comments(id) +async def comments_list(id: int, user: UserTable = Depends(employee)): + return await get_comments(id, user) @router.get("/{id}/comments/{pk}", response_model=Comment, status_code=status.HTTP_200_OK) -async def comment(id: int, pk: int, user: UserTable = Depends(any_user)): - return await get_comment(id, pk) +async def comment(id: int, pk: int, user: UserTable = Depends(employee)): + return await get_comment(id, pk, user) @router.post("/{id}/comments/", response_model=CommentDB, status_code=status.HTTP_201_CREATED) -async def create_comment(id: int, item: CommentCreate, user: UserTable = Depends(any_user)): - return await add_comment(id, item) +async def create_comment(id: int, item: CommentCreate, user: UserTable = Depends(employee)): + return await add_comment(id, item, user) @router.put("/{id}/comments/{pk}", response_model=CommentDB, status_code=status.HTTP_201_CREATED) -async def update_comment_by_id(id: int, item: CommentCreate, user: UserTable = Depends(any_user)): - return await update_comment(id, item) +async def update_comment_by_id(id: int, item: CommentUpdate, user: UserTable = Depends(employee)): + return await update_comment(id, item, user) @router.delete("/{id}/comments/{pk}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_comment_by_id(id: int, user: UserTable = Depends(any_user)): - return await delete_comment(id) +async def delete_comment_by_id(id: int, user: UserTable = Depends(employee)): + return await delete_comment(id, user) diff --git a/src/desk/schemas.py b/src/desk/schemas.py index de17d8c..a31b21b 100644 --- a/src/desk/schemas.py +++ b/src/desk/schemas.py @@ -1,41 +1,42 @@ -from typing import List, Optional +from typing import Optional, List from datetime import datetime from .models import StatusTasks from pydantic import BaseModel -from ..reference_book.schemas import SoftwareShort, ModuleShort + +from ..client_account.schemas import ClientDB +from ..reference_book.schemas import SoftwareDB, ModuleShort +from ..users.schemas import UserDB class AppealBase(BaseModel): topic: str text: str - status: StatusTasks class AppealCreate(AppealBase): - author_id: str - responsible_id: Optional[str] software_id: int module_id: int class AppealUpdate(AppealCreate): - date_processing: Optional[datetime] = None + pass -class AppealDB(AppealCreate): # TODO проверить нужен ли вообще +class AppealShort(AppealBase): id: int - date_create: datetime - date_processing: Optional[datetime] + status: StatusTasks -class AppealShort(AppealBase): +class AppealDB(AppealCreate): id: int - author_id: str # TODO change to Member + client_id: int + author_id: str + status: StatusTasks date_create: datetime - date_processing: Optional[datetime] = None - responsible_id: str # TODO change to Developer - software: SoftwareShort - module: ModuleShort + date_processing: Optional[datetime] + responsible_id: Optional[int] + software_id: int + module_id: int class CommentBase(BaseModel): @@ -43,35 +44,41 @@ class CommentBase(BaseModel): class CommentCreate(CommentBase): - author_id: int + pass + + +class CommentUpdate(CommentCreate): + pass class Comment(CommentBase): id: int + appeal_id: int + author: UserDB date_create: datetime - appeal: AppealShort - author: int # TODO maybe change to member (Union[member, developer]) class CommentDB(CommentBase): id: int - date_create: datetime appeal_id: int - author_id: int + author_id: str + date_create: datetime class CommentShort(CommentBase): id: int + author_id: str date_create: datetime - author: int # TODO maybe change to member (Union[member, developer]) class Appeal(AppealBase): id: int - author: int # TODO change to Member + status: StatusTasks date_create: datetime date_processing: Optional[datetime] = None - responsible: int # TODO change to Developer - software: SoftwareShort + client: ClientDB + author: UserDB + responsible: Optional[UserDB] + software: SoftwareDB module: ModuleShort - # comments: List[CommentShort] = None + comment: Optional[List[CommentShort]] diff --git a/src/desk/services.py b/src/desk/services.py index 97c9df3..478a7e2 100644 --- a/src/desk/services.py +++ b/src/desk/services.py @@ -1,73 +1,112 @@ -from .schemas import AppealCreate, CommentCreate, AppealUpdate +from .schemas import AppealCreate, CommentCreate, AppealUpdate, CommentUpdate +from ..client_account.models import clients from ..db.db import database from .models import appeals, comments from ..reference_book.models import softwares, modules +from ..users.models import UserTable, users +from .models import StatusTasks -async def get_appeals(): +async def get_all_appeals(): result = await database.fetch_all(query=appeals.select()) return [dict(appeal) for appeal in result] -async def get_appeal(id: int): - query = appeals.select().where(appeals.c.id == id) +async def get_appeals(user: UserTable): + query = appeals.select().where(appeals.c.client_id == user.client_id) + result = await database.fetch_all(query=query) + return [dict(appeal) for appeal in result] + + +async def get_appeal(id: int, user: UserTable): + query = appeals.select().where((appeals.c.id == id) & (appeals.c.client_id == user.client_id)) appeal = await database.fetch_one(query=query) if appeal: appeal = dict(appeal) - # author = await database.fetch_one(query=members.select().where(members.c.id == appeal["author"])) - # responsible = await database.fetch_one(query=developers.select().where(developers.c.id == appeal["responsible"])) - software = await database.fetch_one(query=softwares.select().where(softwares.c.id == appeal["software_id"])) - module = await database.fetch_one(query=modules.select().where(modules.c.id == appeal["module_id"])) - return {**appeal, "software": software, "module": module} + # TODO сделать проверку на None + client = dict(await database.fetch_one(query=clients.select().where(clients.c.id == appeal["client_id"]))) + author = dict(await database.fetch_one(query=users.select().where(users.c.id == appeal["author_id"]))) + responsible = await database.fetch_one(query=users.select().where(users.c.id == appeal["responsible_id"])) + software = dict(await database.fetch_one(query=softwares.select().where(softwares.c.id == appeal["software_id"]))) + module = dict(await database.fetch_one(query=modules.select().where(modules.c.id == appeal["module_id"]))) + comment = await get_comments(id, user) + return {**appeal, + "client": client, + "author": author, + "responsible": responsible, + "software": software, + "module": module, + "comment": comment} return None -async def get_comments(id: int): - query = comments.select().where(comments.c.appeal == id) +async def get_appeal_db(id: int): + query = appeals.select().where(appeals.c.id == id) + result = await database.fetch_one(query=query) + return dict(result) + + +async def add_appeal(appeal: AppealCreate, user: UserTable): + item = {**appeal.dict(), "client_id": int(user.client_id), "author_id": str(user.id), "status": StatusTasks.new} # TODO убрать статус, после того, как настрою default + query = appeals.insert().values(item) + appeal_id = await database.execute(query) + return await get_appeal_db(appeal_id) + + +async def update_appeal(id: int, appeal: AppealUpdate, user: UserTable): + query = appeals.update().\ + where((appeals.c.id == id) & (appeals.c.client_id == user.client_id)).\ + values(**appeal.dict()) + result_id = await database.execute(query) + return await get_appeal_db(result_id) + + +async def delete_appeal(id: int): + query = appeals.delete().where(appeals.c.id == id) + result = await database.execute(query) + return result + + +async def get_comments(id: int, user: UserTable): + # TODO проверка на пользователя имеющего доступ: (appeals.c.client_id == user.client_id) + query = comments.select().where(comments.c.appeal_id == id) result = await database.fetch_all(query) result = [dict(comment) for comment in result] return result -async def get_comment(id: int, pk: int): - query = comments.select().where((comments.c.id == pk) & (comments.c.appeal == id)) - result = await database.fetch_one(query) - if result: - return dict(result) +async def get_comment(id: int, pk: int, user: UserTable): + # TODO проверка на пользователя имеющего доступ: (appeals.c.client_id == user.client_id) + query = comments.select().where((comments.c.id == pk) & (comments.c.appeal_id == id)) + comment = await database.fetch_one(query) + if comment: + comment = dict(comment) + author = await database.fetch_one(query=users.select().where(users.c.id == comment["author_id"])) + return {**comment, "author": author} return None -async def add_appeal(appeal: AppealCreate): - query = appeals.insert().values(**appeal.dict()) - id = await database.execute(query) - return await get_appeal(id) +async def get_comment_db(id: int): + query = comments.select().where(comments.c.id == id) + return dict(await database.fetch_one(query=query)) -async def add_comment(appeal_id: int, comment: CommentCreate): - query = comments.insert().values({**comment.dict(), "appeal_id": appeal_id}) +async def add_comment(appeal_id: int, comment: CommentCreate, user: UserTable): + # TODO проверка на пользователя имеющего доступ: (appeals.c.client_id == user.client_id) + item = {**comment.dict(), "appeal_id": int(appeal_id), "author_id": str(user.id)} + query = comments.insert().values(item) comment_id = await database.execute(query) - return {**comment.dict(), "appeal_id": appeal_id, "id": comment_id} - + return await get_comment_db(comment_id) -async def update_appeal(id: int, appeal: AppealUpdate): - query = appeals.update().where(appeals.c.id == id).values(**appeal.dict()) - id_result = await database.execute(query) - return await get_appeal(id_result) - -async def update_comment(id: int, comment: CommentCreate): - query = comments.update().where(comments.c.id == id).values(**comment.dict()) - result = await database.execute(query) - return result - - -async def delete_appeal(id: int): - query = appeals.delete().where(appeals.c.id == id) - result = await database.execute(query) - return result +async def update_comment(id: int, comment: CommentUpdate, user: UserTable): + query = comments.update().where((comments.c.id == id) & (comments.c.author_id == user.id)).values(**comment.dict()) + result_id = await database.execute(query) + return await get_comment_db(result_id) -async def delete_comment(id: int): - query = comments.delete().where(comments.c.id == id) +async def delete_comment(id: int, user: UserTable): + query = comments.delete().where((comments.c.id == id) & (comments.c.author_id == str(user.id))) result = await database.execute(query) + print(result) return result From 170b4e62e8db415edad0e237348c357e8435c7e9 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Mon, 26 Apr 2021 19:27:44 +0500 Subject: [PATCH 20/55] =?UTF-8?q?=D0=9D=D0=B0=D0=BF=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=D0=BB=20CRUD=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D1=8B=20=D0=B8?= =?UTF-8?q?=20=D1=80=D0=B0=D0=B7=D0=B3=D1=80=D0=B0=D0=BD=D0=B8=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=B0?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=9B=D0=9A=20=D0=BA=D0=BB=D0=B8=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client_account/models.py | 6 +----- src/client_account/routers.py | 28 +++++++++++++++++++++------- src/client_account/schemas.py | 23 ++++++++++++++++++----- src/client_account/services.py | 27 +++++++++++++++++++++++---- 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/src/client_account/models.py b/src/client_account/models.py index 5f0e90e..c42e560 100644 --- a/src/client_account/models.py +++ b/src/client_account/models.py @@ -11,12 +11,8 @@ class Client(Base): is_active = Column(Boolean, default=True, nullable=False) date_create = Column(DateTime(timezone=True), server_default=sql.func.now()) date_block = Column(DateTime, default=None, nullable=True) - owner_id = Column(String, ForeignKey('employee.id'), nullable=False) + owner_id = Column(String, ForeignKey('user.id'), nullable=False) # avatar - # employees = relationship('Employee') - # licence = relationship('Licence') - # software = relationship('Software') - clients = Client.__table__ diff --git a/src/client_account/routers.py b/src/client_account/routers.py index 4f0c368..d02c61e 100644 --- a/src/client_account/routers.py +++ b/src/client_account/routers.py @@ -1,17 +1,31 @@ from typing import List -from fastapi import APIRouter -from .schemas import ClientShort, Client -from .services import get_clients, get_client +from fastapi import APIRouter, Depends, status +from .schemas import ClientDB, Client, ClientCreate, ClientUpdate +from .services import get_clients, get_client, add_client +from ..users.models import UserTable +from ..users.logic import developer_user, any_user, get_client_users_with_superuser, get_owner router = APIRouter() -@router.get("/", response_model=List[ClientShort]) -async def clients_list(): +@router.get("/", response_model=List[ClientDB], status_code=status.HTTP_200_OK) +async def clients_list(user: UserTable = Depends(developer_user)): return await get_clients() -@router.get("/{id}", response_model=Client) -async def client(id: int): +@router.post("/", response_model=ClientDB, status_code=status.HTTP_201_CREATED) +async def create_client(item: ClientCreate, user: UserTable = Depends(developer_user)): + return await add_client(item) + + +@router.get("/{id}", response_model=Client, status_code=status.HTTP_200_OK) +async def client(id: int, user: UserTable = Depends(any_user)): + user = await get_client_users_with_superuser(id, user) return await get_client(id) + + +# @router.put("/{id}", response_model=Client, status_code=status.HTTP_201_CREATED) +# async def client(id: int, item: ClientUpdate, user: UserTable = Depends(get_owner)): +# user = get_client_users_with_superuser(id, user) +# return await update_client(id, item) diff --git a/src/client_account/schemas.py b/src/client_account/schemas.py index 39be2d5..700624e 100644 --- a/src/client_account/schemas.py +++ b/src/client_account/schemas.py @@ -1,15 +1,23 @@ from datetime import datetime +from typing import Optional, List from pydantic import BaseModel -from ..users.schemas import User + +from ..reference_book.schemas import LicenceDB +from ..users.schemas import UserDB class ClientBase(BaseModel): name: str - date_block: datetime class ClientCreate(ClientBase): + is_active: bool = True # TODO убрать, когда починю default + owner_id: str + + +class ClientUpdate(BaseModel): + # avatar pass @@ -17,10 +25,15 @@ class Client(ClientBase): id: int is_active: bool date_create: datetime - owner: User + date_block: Optional[datetime] + owner: UserDB + employees: List[UserDB] + licences: List[LicenceDB] -class ClientShort(ClientBase): +class ClientDB(ClientBase): id: int - owner_id: str + is_active: bool date_create: datetime + date_block: Optional[datetime] + owner_id: str diff --git a/src/client_account/services.py b/src/client_account/services.py index 6606496..37bb5f3 100644 --- a/src/client_account/services.py +++ b/src/client_account/services.py @@ -1,6 +1,8 @@ +from .schemas import ClientCreate from ..db.db import database from .models import clients -from ..users.models import users +from ..reference_book.models import licences +from ..users.models import users, UserTable async def get_clients(): @@ -13,6 +15,23 @@ async def get_client(id: int): result = await database.fetch_one(query=query) if result: result = dict(result) - query = users.select().where(users.c.id == result["owner_id"]) - owner = await database.fetch_one(query=query) - return {**result, "owner": owner} + owner = dict(await database.fetch_one(query=users.select().where(users.c.id == result["owner_id"]))) + employee_data = await database.fetch_all(query=users.select().where(users.c.client_id == id)) + employees = [dict(employee) for employee in employee_data] + licence_data = await database.fetch_all(query=licences.select().where(licences.c.client_id == id)) + licences_dict = [dict(licence) for licence in licence_data] + return {**result, + "owner": owner, + "employees": employees, + "licences": licences_dict} + + +async def get_client_db(client_id): + query = clients.select().where(clients.c.id == client_id) + return dict(await database.fetch_one(query=query)) + + +async def add_client(client: ClientCreate): + query = clients.insert().values(client.dict()) + client_id = await database.execute(query) + return await get_client_db(client_id) From ef7000507c5e0590bafa4ca61a31f0909a932f0c Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Tue, 27 Apr 2021 13:26:04 +0500 Subject: [PATCH 21/55] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=80=D0=BE=D1=83=D1=82=D1=8B=20=D0=B8=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=B0=20=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=B0?= =?UTF-8?q?=20=D0=BA=20=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client_account/routers.py | 4 + src/reference_book/api/licence.py | 44 +++++++---- src/reference_book/api/module.py | 39 ++++------ src/reference_book/api/routes.py | 26 +++++-- src/reference_book/api/software.py | 28 ++++--- src/reference_book/schemas.py | 28 ++----- src/reference_book/services.py | 120 +++++++++++++++++------------ 7 files changed, 165 insertions(+), 124 deletions(-) diff --git a/src/client_account/routers.py b/src/client_account/routers.py index d02c61e..98c878d 100644 --- a/src/client_account/routers.py +++ b/src/client_account/routers.py @@ -5,6 +5,7 @@ from .services import get_clients, get_client, add_client from ..users.models import UserTable from ..users.logic import developer_user, any_user, get_client_users_with_superuser, get_owner +from src.reference_book.api.licence import for_client_router router = APIRouter() @@ -29,3 +30,6 @@ async def client(id: int, user: UserTable = Depends(any_user)): # async def client(id: int, item: ClientUpdate, user: UserTable = Depends(get_owner)): # user = get_client_users_with_superuser(id, user) # return await update_client(id, item) + + +router.include_router(for_client_router) diff --git a/src/reference_book/api/licence.py b/src/reference_book/api/licence.py index ae0dd1d..251d6ef 100644 --- a/src/reference_book/api/licence.py +++ b/src/reference_book/api/licence.py @@ -1,32 +1,46 @@ from typing import List -from fastapi import APIRouter, status -from ..schemas import Licence, LicenceShort, LicenceCreate, LicenceDB -from ..services import get_licence, get_licences, add_licence, delete_licence, update_licence +from fastapi import APIRouter, Depends, status, Response +from ..schemas import Licence, LicenceCreate, LicenceDB +from ..services import get_licence, get_licences, \ + get_client_licences, get_client_licence, add_client_licence, update_client_licence, delete_client_licence +from ...users.models import UserTable +from ...users.logic import developer_user router = APIRouter() +for_client_router = APIRouter() -@router.get("/", response_model=List[LicenceShort], status_code=status.HTTP_200_OK) -async def licence_list(): +@router.get("/", response_model=List[LicenceDB], status_code=status.HTTP_200_OK) +async def licence_list(user: UserTable = Depends(developer_user)): return await get_licences() @router.get('/{id}', response_model=Licence, status_code=status.HTTP_200_OK) -async def licence(id: int): +async def licence(id: int, user: UserTable = Depends(developer_user)): return await get_licence(id) -@router.post("/", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) -async def create_licence(licence: LicenceCreate): - return await add_licence(licence) +@for_client_router.get("/{id}/licences", response_model=List[LicenceDB], status_code=status.HTTP_200_OK) +async def client_licence_list(id: int, user: UserTable = Depends(developer_user)): + return await get_client_licences(id) -@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_licence_by_id(id: int): - return await delete_licence(id) +@for_client_router.get('/{id}/licences/{pk}', response_model=Licence, status_code=status.HTTP_200_OK) +async def client_licence(id: int, pk: int, user: UserTable = Depends(developer_user)): + return await get_client_licence(id, pk) -@router.put("/{id}", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) -async def update_licence_by_id(id: int, item: LicenceCreate): - return await update_licence(id, item) +@for_client_router.post("/{id}/licences/", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) +async def create_client_licence(id: int, licence: LicenceCreate, user: UserTable = Depends(developer_user)): + return await add_client_licence(id, licence) + + +@for_client_router.put("/{id}/licences/{pk}", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) +async def update_client_licence_by_id(id: int, pk: int, item: LicenceCreate, user: UserTable = Depends(developer_user)): + return await update_client_licence(id, pk, item) + + +@for_client_router.delete("/{id}/licences/{pk}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) +async def delete_client_licence_by_id(id: int, pk: int, user: UserTable = Depends(developer_user)): + await delete_client_licence(id, pk) diff --git a/src/reference_book/api/module.py b/src/reference_book/api/module.py index e69893e..c059bb5 100644 --- a/src/reference_book/api/module.py +++ b/src/reference_book/api/module.py @@ -1,32 +1,27 @@ -from typing import List - -from fastapi import APIRouter, status -from ..schemas import Module, ModuleShort, ModuleCreate, ModuleDB -from ..services import get_module, get_modules, add_module, delete_module, update_module +from fastapi import APIRouter, status, Depends, Response +from ..schemas import Module, ModuleCreate, ModuleDB +from ..services import get_module, add_module, delete_module, update_module +from ...users.logic import developer_user +from ...users.models import UserTable router = APIRouter() -@router.get("/", response_model=List[ModuleShort], status_code=status.HTTP_200_OK) -async def module_list(): - return await get_modules() - - -@router.get('/{id}', response_model=Module, status_code=status.HTTP_200_OK) -async def module(id: int): - return await get_module(id) +@router.get('/{id}/modules/{pk}', response_model=Module, status_code=status.HTTP_200_OK) +async def module(id: int, pk: int, user: UserTable = Depends(developer_user)): + return await get_module(id, pk) -@router.post("/", response_model=ModuleDB, status_code=status.HTTP_201_CREATED) -async def create_module(item: ModuleCreate): - return await add_module(item) +@router.post("/{id}/modules/", response_model=ModuleDB, status_code=status.HTTP_201_CREATED) +async def create_module(id, item: ModuleCreate, user: UserTable = Depends(developer_user)): + return await add_module(id, item) -@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_module_by_id(id: int): - return await delete_module(id) +@router.put("/{id}/modules/{pk}", response_model=ModuleDB, status_code=status.HTTP_201_CREATED) +async def update_module_by_id(id: int, pk: int, item: ModuleCreate, user: UserTable = Depends(developer_user)): + return await update_module(id, pk, item) -@router.put("/{id}", response_model=ModuleDB, status_code=status.HTTP_201_CREATED) -async def update_module_by_id(id: int, item: ModuleCreate): - return await update_module(id, item) +@router.delete("/{id}/modules/{pk}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) +async def delete_module_by_id(id: int, pk: int, user: UserTable = Depends(developer_user)): + await delete_module(id, pk) diff --git a/src/reference_book/api/routes.py b/src/reference_book/api/routes.py index e87a839..0c1adb4 100644 --- a/src/reference_book/api/routes.py +++ b/src/reference_book/api/routes.py @@ -1,11 +1,27 @@ -from fastapi import APIRouter -from .module import router as module_router -from .software import router as software_router -from .licence import router as licence_router +from typing import List + +from fastapi import APIRouter, Depends, status +from .software import router as software_router, software_list +from .licence import router as licence_router, licence_list +from ..schemas import ModuleShort +from ..services import get_modules +from ...users.logic import developer_user +from ...users.models import UserTable router = APIRouter() -router.include_router(module_router, prefix='/modules') +@router.get("/modules", response_model=List[ModuleShort], status_code=status.HTTP_200_OK) +async def module_list(user: UserTable = Depends(developer_user)): + return await get_modules() + + +@router.get('/', status_code=status.HTTP_200_OK) +async def get_reference_book(user: UserTable = Depends(developer_user)): + licences = await licence_list(user) + modules = await module_list(user) + softwares = await software_list(user) + return {"licences": licences, "modules": modules, "softwares": softwares} + router.include_router(licence_router, prefix='/licences') router.include_router(software_router, prefix='/software') diff --git a/src/reference_book/api/software.py b/src/reference_book/api/software.py index 0fbc15a..a107008 100644 --- a/src/reference_book/api/software.py +++ b/src/reference_book/api/software.py @@ -1,39 +1,45 @@ from typing import List -from fastapi import APIRouter, status +from fastapi import APIRouter, status, Depends, Response from ..services import get_software, get_softwares, get_software_with_modules, \ add_software, delete_software, update_software from ..schemas import Software, SoftwareDB, SoftwareCreate +from ...users.logic import developer_user +from ...users.models import UserTable +from .module import router as module_router router = APIRouter() @router.get('/', response_model=List[SoftwareDB], status_code=status.HTTP_200_OK) -async def software_list(): +async def software_list(user: UserTable = Depends(developer_user)): return await get_softwares() @router.get('/{id}', response_model=SoftwareDB, status_code=status.HTTP_200_OK) -async def software(id: int): +async def software(id: int, user: UserTable = Depends(developer_user)): return await get_software(id) @router.get("/{id}/modules", response_model=Software, status_code=status.HTTP_200_OK) -async def get_software_modules(id: int): +async def get_software_modules(id: int, user: UserTable = Depends(developer_user)): return await get_software_with_modules(id) @router.post("/", response_model=SoftwareDB, status_code=status.HTTP_201_CREATED) -async def create_software(software: SoftwareCreate): +async def create_software(software: SoftwareCreate, user: UserTable = Depends(developer_user)): return await add_software(software) -@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_software_by_id(id: int): - return await delete_software(id) - - @router.put("/{id}", response_model=SoftwareDB, status_code=status.HTTP_201_CREATED) -async def update_software_by_id(id: int, item: SoftwareCreate): +async def update_software_by_id(id: int, item: SoftwareCreate, user: UserTable = Depends(developer_user)): return await update_software(id, item) + + +@router.delete("/{id}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) +async def delete_software_by_id(id: int, user: UserTable = Depends(developer_user)): + await delete_software(id) + + +router.include_router(module_router) diff --git a/src/reference_book/schemas.py b/src/reference_book/schemas.py index 366c414..0efa9d6 100644 --- a/src/reference_book/schemas.py +++ b/src/reference_book/schemas.py @@ -28,17 +28,13 @@ class LicenceBase(BaseModel): class LicenceCreate(LicenceBase): - client_id: int software_id: int -class LicenceShort(LicenceBase): - id: int - client_id: int # TODO заменить на Client - software_id: int # TODO заменить на str - - class Config: - orm_mode = True +# class LicenceShort(LicenceBase): +# id: int +# client_id: int +# software_id: int # TODO заменить на str class LicenceDB(LicenceBase): @@ -49,19 +45,16 @@ class LicenceDB(LicenceBase): class Licence(LicenceBase): id: int - client_id: int + client_id: int # TODO заменить на Client software: SoftwareDB - class Config: - orm_mode = True - class ModuleBase(BaseModel): name: str class ModuleCreate(ModuleBase): - software_id: int + pass class ModuleDB(ModuleBase): @@ -73,20 +66,11 @@ class Module(ModuleBase): id: int software: SoftwareDB - class Config: - orm_mode = True - class ModuleShort(ModuleBase): id: int - class Config: - orm_mode = True - class Software(SoftwareBase): id: int modules: List[ModuleShort] = None - - class Config: - orm_mode = True diff --git a/src/reference_book/services.py b/src/reference_book/services.py index eb7180f..b32510e 100644 --- a/src/reference_book/services.py +++ b/src/reference_book/services.py @@ -1,10 +1,7 @@ -from datetime import datetime - -from .schemas import ModuleCreate, LicenceCreate, LicenceShort, SoftwareCreate +from .schemas import ModuleCreate, LicenceCreate, SoftwareCreate from ..client_account.services import get_client from ..db.db import database from .models import softwares, modules, licences -from sqlalchemy.sql import select async def get_softwares(): @@ -19,33 +16,30 @@ async def get_software(id: int): return None -async def add_software(software: SoftwareCreate): - query = softwares.insert().values(**software.dict()) - id = await database.execute(query) - return {**software.dict(), "id": id} +async def get_software_with_modules(id: int): + result = await database.fetch_one(query=softwares.select().where(softwares.c.id == id)) + if result is not None: + result = dict(result) + module = await get_software_modules(id) + return {**result, "modules": module} + return None -async def add_licence(licence: LicenceCreate): - query = licences.insert().values(**licence.dict()) +async def add_software(software: SoftwareCreate): + query = softwares.insert().values(**software.dict()) id = await database.execute(query) - return {**licence.dict(), "id": id} + return {"id": id, **software.dict()} -async def add_module(module: ModuleCreate): - query = modules.insert().values(**module.dict()) - id = await database.execute(query) - return {**module.dict(), "id": id} +async def update_software(id: int, software: SoftwareCreate): + query = softwares.update().where(softwares.c.id == id).values(**software.dict()) + await database.execute(query) + return {"id": id, **software.dict()} -async def get_software_with_modules(id: int): - result = await database.fetch_one(query=softwares.select().where(softwares.c.id == id)) - query = select([modules.c.id, modules.c.name]).select_from(modules).where(modules.c.software_id == id) - module = await database.fetch_all(query) - if result is not None: - result = dict(result) - module = [dict(m) for m in module] - return {**result, "modules": module} - return None +async def delete_software(id: int): + query = softwares.delete().where(softwares.c.id == id) + await database.execute(query) async def get_modules(): @@ -53,15 +47,42 @@ async def get_modules(): return [dict(module) for module in result] -async def get_module(id: int): - result = await database.fetch_one(query=modules.select().where(modules.c.id == id)) +async def get_software_modules(id: int): + result = await database.fetch_all(query=modules.select().where(modules.c.software_id == id)) + return [dict(module) for module in result] + + +async def get_module(id: int, pk: int): + query = modules.select().where((modules.c.software_id == id) & (modules.c.id == pk)) + result = await database.fetch_one(query=query) if result is not None: module = dict(result) - software = await get_software(module["software_id"]) + software = await get_software(id) return {**module, "software": software} return None +async def get_module_db(id: int): + return dict(await database.fetch_one(modules.select().where(modules.c.id == id))) + + +async def add_module(id: int, module: ModuleCreate): # TODO посмотреть, что лучше работает, словарь или вызов бд + query = modules.insert().values({**module.dict(), "software_id": id}) + module_id = await database.execute(query) + return {"id": module_id, **module.dict(), "software_id": id} + + +async def update_module(id: int, pk: int, module: ModuleCreate): + query = modules.update().where((modules.c.software_id == id) & (modules.c.id == pk)).values(**module.dict()) + await database.execute(query) + return {"id": pk, **module.dict(), "software_id": id} + + +async def delete_module(id: int, pk: int): + query = modules.delete().where((modules.c.software_id == id) & (modules.c.id == pk)) + await database.execute(query) + + async def get_licences(): result = await database.fetch_all(query=licences.select()) return [dict(licence) for licence in result] @@ -77,33 +98,34 @@ async def get_licence(id: int): return None -async def update_module(id: int, module: ModuleCreate): - query = modules.update().where(modules.c.id == id).values(**module.dict()) - id_result = await database.execute(query) - return id_result - - -async def update_licence(id: int, licence: LicenceCreate): - query = licences.update().where(licences.c.id == id).values(**licence.dict()) - return await database.execute(query) +async def get_client_licences(id: int): + result = await database.fetch_all(query=licences.select().where(licences.c.client_id == id)) + return [dict(licence) for licence in result] -async def update_software(id: int, software: SoftwareCreate): - query = softwares.update().where(softwares.c.id == id).values(**software.dict()) - return await database.execute(query) +async def get_client_licence(id: int, pk: int): + query = licences.select().where((licences.c.client_id == id) & (licences.c.id == pk)) + result = await database.fetch_one(query=query) + if result is not None: + licence = dict(result) + software = await get_software(licence["software_id"]) + return {**licence, "software": software} + return None -async def delete_module(id: int): - query = modules.delete().where(modules.c.id == id) - result = await database.execute(query) - return result +async def add_client_licence(id: int, licence: LicenceCreate): + item = {**licence.dict(), "client_id": id} + query = licences.insert().values(item) + licence_id = await database.execute(query) + return {"id": licence_id, **licence.dict(), "client_id": id} -async def delete_licence(id: int): - query = licences.delete().where(licences.c.id == id) - return await database.execute(query) +async def update_client_licence(id: int, pk: int, licence: LicenceCreate): + query = licences.update().where((licences.c.client_id == id) & (licences.c.id == pk)).values(**licence.dict()) + await database.execute(query) + return {"id": pk, **licence.dict(), "client_id": id} -async def delete_software(id: int): - query = softwares.delete().where(softwares.c.id == id) - return await database.execute(query) +async def delete_client_licence(id: int, pk: int): + query = licences.delete().where((licences.c.client_id == id) & (licences.c.id == pk)) + await database.execute(query) From 35afeae74cb1d882274d10f55cc99e439a6a02fe Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Thu, 29 Apr 2021 13:32:49 +0500 Subject: [PATCH 22/55] add client account --- src/accounts/client_account/__init__.py | 0 src/{ => accounts}/client_account/models.py | 4 +- src/accounts/client_account/routers.py | 56 +++++++++ src/{ => accounts}/client_account/schemas.py | 14 ++- src/accounts/client_account/services.py | 119 +++++++++++++++++++ 5 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 src/accounts/client_account/__init__.py rename src/{ => accounts}/client_account/models.py (85%) create mode 100644 src/accounts/client_account/routers.py rename src/{ => accounts}/client_account/schemas.py (61%) create mode 100644 src/accounts/client_account/services.py diff --git a/src/accounts/client_account/__init__.py b/src/accounts/client_account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/client_account/models.py b/src/accounts/client_account/models.py similarity index 85% rename from src/client_account/models.py rename to src/accounts/client_account/models.py index c42e560..3ff1546 100644 --- a/src/client_account/models.py +++ b/src/accounts/client_account/models.py @@ -1,6 +1,6 @@ from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, sql -from ..db.db import Base +from ...db.db import Base class Client(Base): @@ -8,7 +8,7 @@ class Client(Base): id = Column(Integer, primary_key=True, index=True, unique=True) name = Column(String, unique=True, nullable=False) # TODO can be unique? - is_active = Column(Boolean, default=True, nullable=False) + is_active = Column(Boolean, default=False, nullable=False) date_create = Column(DateTime(timezone=True), server_default=sql.func.now()) date_block = Column(DateTime, default=None, nullable=True) owner_id = Column(String, ForeignKey('user.id'), nullable=False) diff --git a/src/accounts/client_account/routers.py b/src/accounts/client_account/routers.py new file mode 100644 index 0000000..61f446f --- /dev/null +++ b/src/accounts/client_account/routers.py @@ -0,0 +1,56 @@ +from typing import List + +from fastapi import APIRouter, Depends, status, Request +from .schemas import ClientDB, Client, ClientCreate, ClientUpdate +from .services import get_clients, get_client, add_client, update_client, add_owner, get_client_owner, update_owner +from src.users.models import UserTable +from src.users.logic import developer_user, any_user, get_client_users_with_superuser, get_owner_with_superuser +from src.reference_book.api.licence import client_licences_router +from ..employee_account.routers import employee_router +from ...users.schemas import UserCreate + +client_router = APIRouter() + + +@client_router.get("/", response_model=List[ClientDB], status_code=status.HTTP_200_OK) +async def clients_list(user: UserTable = Depends(developer_user)): + return await get_clients() + + +@client_router.post("/", response_model=ClientDB, status_code=status.HTTP_201_CREATED) +async def create_client(item: ClientCreate, user: UserTable = Depends(developer_user)): + return await add_client(item) + + +@client_router.get("/{id}", response_model=Client, status_code=status.HTTP_200_OK) +async def client(id: int, user: UserTable = Depends(any_user)): + user = await get_client_users_with_superuser(id, user) + return await get_client(id) + + +@client_router.put("/{id}", response_model=ClientDB, status_code=status.HTTP_201_CREATED) +async def update_client_by_id(id: int, item: ClientUpdate, user: UserTable = Depends(any_user)): + # TODO разделить изменение аватарки владельцем и изменение владельца владельцем или разработчиком + user = get_owner_with_superuser(id, user) + return await update_client(id, item) + + +@client_router.get("/{id}/owner", status_code=status.HTTP_200_OK) +async def owner(id: int, user: UserTable = Depends(any_user)): + user = await get_client_users_with_superuser(id, user) + return await get_client_owner(id) + + +@client_router.post("/{id}/owner", status_code=status.HTTP_201_CREATED) +async def create_owner(id: int, item: UserCreate, user: UserTable = Depends(developer_user)): + return await add_owner(id, item) + + +@client_router.patch("/{id}/owner", status_code=status.HTTP_201_CREATED) +async def change_owner(id: int, item: ClientUpdate, user: UserTable = Depends(developer_user)): + user = get_owner_with_superuser(id, user) + return await update_owner(id, item.owner_id) + + +client_router.include_router(client_licences_router) +client_router.include_router(employee_router) diff --git a/src/client_account/schemas.py b/src/accounts/client_account/schemas.py similarity index 61% rename from src/client_account/schemas.py rename to src/accounts/client_account/schemas.py index 700624e..002abcf 100644 --- a/src/client_account/schemas.py +++ b/src/accounts/client_account/schemas.py @@ -2,9 +2,10 @@ from typing import Optional, List from pydantic import BaseModel +from pydantic.types import UUID4 -from ..reference_book.schemas import LicenceDB -from ..users.schemas import UserDB +from ...reference_book.schemas import LicenceDB, SoftwareDB +from ...users.schemas import UserDB class ClientBase(BaseModel): @@ -12,11 +13,11 @@ class ClientBase(BaseModel): class ClientCreate(ClientBase): - is_active: bool = True # TODO убрать, когда починю default - owner_id: str + pass -class ClientUpdate(BaseModel): +class ClientUpdate(ClientCreate): # TODO доработать изменение заказчика + owner_id: Optional[UUID4] # avatar pass @@ -29,6 +30,7 @@ class Client(ClientBase): owner: UserDB employees: List[UserDB] licences: List[LicenceDB] + software: List[SoftwareDB] class ClientDB(ClientBase): @@ -36,4 +38,4 @@ class ClientDB(ClientBase): is_active: bool date_create: datetime date_block: Optional[datetime] - owner_id: str + owner_id: UUID4 diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py new file mode 100644 index 0000000..b163779 --- /dev/null +++ b/src/accounts/client_account/services.py @@ -0,0 +1,119 @@ +from datetime import datetime + +from fastapi import HTTPException, status, Request +from fastapi.responses import RedirectResponse +from fastapi_users.router import ErrorCode +from pydantic.types import UUID4 + +from .schemas import ClientCreate, ClientUpdate +from ..employee_account.services import update_employee +from ...db.db import database +from .models import clients +from ...errors import Errors +from ...reference_book.models import licences, softwares +from ...users.logic import all_users, get_or_404 +from ...users.models import users, user_db +from ...users.schemas import UserCreate, OwnerCreate, UserUpdate + + +async def check_and_convert_to_dict(result): + if result: + return dict(result) + return [] + + +async def get_clients(): + result = await database.fetch_all(clients.select()) + return [dict(client) for client in result] + + +async def get_client(id: int): + query = clients.select().where(clients.c.id == id) + result = await database.fetch_one(query=query) + if result: + result = dict(result) + owner_data = await database.fetch_one(query=users.select().where(users.c.id == result["owner_id"])) + owner = await check_and_convert_to_dict(owner_data) + employee_data = await database.fetch_all(query=users.select().where(users.c.client_id == id)) + employees = [dict(employee) for employee in employee_data] + licence_data = await database.fetch_all(query=licences.select().where(licences.c.client_id == id)) + licences_dict = [dict(licence) for licence in licence_data] + software = [await database.fetch_one( + softwares.select().where(softwares.c.id == licence["software_id"]) + ) for licence in licence_data] + return {**result, + "owner": owner, + "employees": employees, + "licences": licences_dict, + "software": software} + + +async def get_client_db(client_id): + query = clients.select().where(clients.c.id == client_id) + return dict(await database.fetch_one(query=query)) + + +async def add_client(client: ClientCreate): + query = clients.insert().values({**client.dict(), "is_active": False, "owner_id": "undefined"}) + client_id = await database.execute(query) + return RedirectResponse(f"http://0.0.0.0:8000/clients/{client_id}/owner") + + +async def update_client(id: int, client: ClientUpdate): # TODO проверить работу обновления + query = clients.update().where(clients.c.id == id).values(client.dict()) + client_id = await database.execute(query) + return await get_client_db(client_id) + + +async def activate_client(id: int): + current_client = await database.fetch_one(query=clients.select().where(clients.c.id == id)) + if current_client: + current_client = dict(current_client) + current_client["is_active"] = True + await database.execute(query=clients.update().where(clients.c.id == id).values(**current_client)) + return None + + +async def get_client_owner(client_id: int): + query = users.select().where((users.c.client_id == client_id) & (users.c.is_owner is True)) + owner = await database.fetch_one(query=query) + if owner is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.USER_NOT_FOUND + ) + return dict(owner) + + +async def update_owner(client_id: int, new_owner_id: UUID4): + client = await database.fetch_one(clients.select().where(clients.c.id == client_id)) + if client: + client = dict(client) + if client["owner_id"] == "undefined": + client["owner_id"] = new_owner_id + new_owner = get_or_404(new_owner_id) + else: + update_old = UserUpdate(is_owner=False) + update_new = UserUpdate(is_owner=True) + new_client = ClientUpdate(**client, owner_id=new_owner_id) + await update_employee(client["owner_id"], update_old) + new_owner = await update_employee(new_owner_id, update_new) + await update_client(client_id, new_client) + return new_owner + + +async def add_owner(client_id: int, user: UserCreate): + owner = OwnerCreate( + **user.dict(), + client_id=client_id, + is_owner=True, + date_reg=datetime.utcnow()) + try: + created_owner = await all_users.create_user(owner, safe=True) + except Exception: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, + ) + await update_owner(client_id, created_owner.id) + return RedirectResponse(f"http://0.0.0.0:8000/clients/{client_id}") From 0c260c1c2c10b55a6263fe96850a57359c3da907 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Thu, 29 Apr 2021 13:33:14 +0500 Subject: [PATCH 23/55] add employee account --- src/accounts/employee_account/__init__.py | 0 src/accounts/employee_account/routers.py | 61 +++++++++++++++++++ src/accounts/employee_account/services.py | 71 +++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 src/accounts/employee_account/__init__.py create mode 100644 src/accounts/employee_account/routers.py create mode 100644 src/accounts/employee_account/services.py diff --git a/src/accounts/employee_account/__init__.py b/src/accounts/employee_account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/accounts/employee_account/routers.py b/src/accounts/employee_account/routers.py new file mode 100644 index 0000000..3ce9ffc --- /dev/null +++ b/src/accounts/employee_account/routers.py @@ -0,0 +1,61 @@ +from typing import List + +from fastapi import APIRouter, Request, status, Depends, HTTPException, Response +from pydantic.types import UUID4 + +from .services import add_employee, count_allowed_employees, get_employees, \ + get_employee, update_employee, delete_employee, block_employee +from src.errors import Errors +from src.users.logic import get_owner, any_user, get_client_users_with_superuser +from src.users.models import UserTable +from src.users.schemas import UserCreate, UserDB, UserUpdate + +employee_router = APIRouter() + + +@employee_router.get("/{id}/employees", response_model=List[UserDB], status_code=status.HTTP_200_OK) +async def employees_list(id: int, user: UserTable = Depends(any_user)): + user = await get_client_users_with_superuser(id, user) + return await get_employees(id) + + +@employee_router.get("/{id}/employees/{pk}", response_model=UserDB, status_code=status.HTTP_200_OK) +async def employee(id: int, pk: str, user: UserTable = Depends(any_user)): + user = await get_client_users_with_superuser(id, user) + return await get_employee(id, pk) + + +@employee_router.post("/{id}/employees", response_model=UserDB, status_code=status.HTTP_201_CREATED) +async def create_employee(id: int, item: UserCreate, user: UserTable = Depends(any_user)): + user = await get_owner(id, user) + if await count_allowed_employees(id): + return await add_employee(id, item) + else: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=Errors.NO_VACANCIES_UNDER_LICENCES, + ) + + +@employee_router.patch("/{id}/employees/{pk}", response_model=UserDB, status_code=status.HTTP_201_CREATED) +async def update_employee_by_id(id: int, pk: UUID4, item: UserUpdate, user: UserTable = Depends(any_user)): + user = await get_owner(id, user) + return await update_employee(pk, item) + + +@employee_router.patch("/{id}/employees/{pk}/block", response_model=UserDB, status_code=status.HTTP_201_CREATED) +async def block_employee_by_id(id: int, pk: UUID4, user: UserTable = Depends(any_user)): + user = await get_owner(id, user) + return await block_employee(pk) + + +@employee_router.delete("/{id}/employees/{pk}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) +async def delete_employee_by_id(id: int, pk: UUID4, user: UserTable = Depends(any_user)): + user = await get_owner(id, user) + if user.id == pk: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=Errors.IMPOSSIBLE_DELETE_OWNER, + ) + + await delete_employee(pk) diff --git a/src/accounts/employee_account/services.py b/src/accounts/employee_account/services.py new file mode 100644 index 0000000..ec2fb46 --- /dev/null +++ b/src/accounts/employee_account/services.py @@ -0,0 +1,71 @@ +from datetime import datetime + +from fastapi import HTTPException, status, Request +from fastapi_users.router import ErrorCode +from pydantic.types import UUID4 + +from src.db.db import database +from src.reference_book.models import licences +from src.users.logic import all_users, update_user, delete_user +from src.users.models import users +from src.users.schemas import UserCreate, EmployeeCreate, UserUpdate + + +async def get_employees(id: int): + employees_data = await database.fetch_all(users.select().where(users.c.client_id == id)) + result = [dict(employee) for employee in employees_data] + return result + + +async def get_employee(id: int, pk: str): + employee = await database.fetch_one(users.select().where((users.c.client_id == id) & (users.c.id == pk))) + if employee: + employee = dict(employee) + return employee + return None + + +async def count_allowed_employees(client_id: int): + count = 0 + licence_data = await database.fetch_all(query=licences.select().where(licences.c.client_id == client_id)) + licences_dict = [dict(licence) for licence in licence_data] + for licence in licences_dict: + count += licence["count_members"] + + query = users.select().where((users.c.client_id == client_id) & (users.c.is_active is True)) + employee_data = await database.fetch_all(query=query) + employee_dict = [dict(employee) for employee in employee_data] + return count - len(employee_dict) + + +async def add_employee(id: int, user: UserCreate): + employee = EmployeeCreate( + **user.dict(), + client_id=id, + is_owner=False, + date_reg=datetime.utcnow()) + try: + created_user = await all_users.create_user(employee, safe=True) + except Exception: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, + ) + + return created_user + + +async def update_employee(pk: UUID4, item: UserUpdate): + update_dict = item.dict(exclude_unset=True) + updated_employee = await update_user(pk, update_dict) + return updated_employee + + +async def delete_employee(pk: UUID4): + await delete_user(pk) + + +async def block_employee(pk: UUID4): + update_dict = {"is_active": False} + updated_employee = await update_user(pk, update_dict) + return updated_employee From a22e9b1c53e523083389dd84a6b0f0c3765f756f Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Thu, 29 Apr 2021 13:33:42 +0500 Subject: [PATCH 24/55] add developers account --- src/accounts/developer_account/__init__.py | 0 src/accounts/developer_account/routers.py | 36 ++++++++++++++++ src/accounts/developer_account/services.py | 48 ++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 src/accounts/developer_account/__init__.py create mode 100644 src/accounts/developer_account/routers.py create mode 100644 src/accounts/developer_account/services.py diff --git a/src/accounts/developer_account/__init__.py b/src/accounts/developer_account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/accounts/developer_account/routers.py b/src/accounts/developer_account/routers.py new file mode 100644 index 0000000..a1f2b9d --- /dev/null +++ b/src/accounts/developer_account/routers.py @@ -0,0 +1,36 @@ +from typing import List + +from fastapi import APIRouter, Depends, status, Response +from pydantic.types import UUID4 + +from .services import get_developers, get_developer, add_developer, update_developer, delete_developer +from src.users.models import UserTable +from src.users.logic import developer_user +from src.users.schemas import UserDB, UserCreate, UserUpdate + +developer_router = APIRouter() + + +@developer_router.get("/", response_model=List[UserDB], status_code=status.HTTP_200_OK) +async def developers_list(user: UserTable = Depends(developer_user)): + return await get_developers() + + +@developer_router.get("/{id:uuid}", response_model=UserDB, status_code=status.HTTP_200_OK) +async def developer(id: UUID4, user: UserTable = Depends(developer_user)): + return await get_developer(id) + + +@developer_router.post("/") +async def create_developer(item: UserCreate, user: UserTable = Depends(developer_user)): + return await add_developer(item) + + +@developer_router.patch("/{id:uuid}", response_model=UserDB, status_code=status.HTTP_201_CREATED) +async def update_developer_by_id(id: UUID4, item: UserUpdate, user: UserTable = Depends(developer_user)): + return await update_developer(id, item) + + +@developer_router.delete("/{id:uuid}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) +async def delete_developer_by_id(id: UUID4, user: UserTable = Depends(developer_user)): + await delete_developer(id) diff --git a/src/accounts/developer_account/services.py b/src/accounts/developer_account/services.py new file mode 100644 index 0000000..77f9194 --- /dev/null +++ b/src/accounts/developer_account/services.py @@ -0,0 +1,48 @@ +from datetime import datetime + +from fastapi import Request, HTTPException, status +from fastapi_users.router import ErrorCode +from pydantic.types import UUID4 + +from src.db.db import database +from src.users.logic import all_users, delete_user, update_user +from src.users.models import users +from src.users.schemas import UserCreate, DeveloperCreate, UserUpdate + + +async def get_developers(): + developers_data = await database.fetch_all(users.select().where(users.c.is_superuser is True)) + return [dict(developer) for developer in developers_data] + + +async def get_developer(id: UUID4): + developer = await database.fetch_one(users.select().where((users.c.is_superuser is True) & (users.c.id == id))) + if developer: + developer = dict(developer) + return developer + return None + + +async def add_developer(user: UserCreate): + developer = DeveloperCreate( + **user.dict(), + date_reg=datetime.utcnow()) + try: + created_developer = await all_users.create_user(developer, safe=True) + except Exception: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, + ) + + return created_developer + + +async def update_developer(id: UUID4, item: UserUpdate): + update_dict = item.dict(exclude_unset=True) + updated_developer = await update_user(id, update_dict) + return updated_developer + + +async def delete_developer(id: UUID4): + await delete_user(id) From acadaceb00526a4baae88bec0c5436613f530de4 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Thu, 29 Apr 2021 13:35:13 +0500 Subject: [PATCH 25/55] update routes --- src/{client_account => accounts}/__init__.py | 0 src/accounts/api.py | 8 ++++++++ src/app.py | 10 ++++++---- src/db/base.py | 2 +- src/desk/dev_routes.py | 14 ++++++++++++++ src/desk/schemas.py | 2 +- src/desk/services.py | 4 ++-- src/reference_book/api/licence.py | 12 ++++++------ src/reference_book/schemas.py | 4 +--- src/reference_book/services.py | 6 +++--- 10 files changed, 42 insertions(+), 20 deletions(-) rename src/{client_account => accounts}/__init__.py (100%) create mode 100644 src/accounts/api.py create mode 100644 src/desk/dev_routes.py diff --git a/src/client_account/__init__.py b/src/accounts/__init__.py similarity index 100% rename from src/client_account/__init__.py rename to src/accounts/__init__.py diff --git a/src/accounts/api.py b/src/accounts/api.py new file mode 100644 index 0000000..5f568ed --- /dev/null +++ b/src/accounts/api.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter +from .client_account.routers import client_router as client_router +from .developer_account.routers import developer_router + +accounts_router = APIRouter() + +accounts_router.include_router(client_router, prefix='/clients', tags=['Client']) +accounts_router.include_router(developer_router, prefix='/developers', tags=['Developer']) diff --git a/src/app.py b/src/app.py index fd7027e..fcef522 100644 --- a/src/app.py +++ b/src/app.py @@ -2,8 +2,9 @@ from src.db.db import database, engine from .db.base import Base -from .client_account.routers import router as client_router +from .accounts.api import accounts_router from .desk.routes import router as desk_router +from .desk.dev_routes import dev_router from .reference_book.api.routes import router as book_router from .users.routes import router as users_routes @@ -21,12 +22,13 @@ async def shutdown(): await database.disconnect() -@app.get('/') +@app.get('/', tags=['Home']) def read_root(): return {'Hello': 'World'} -app.include_router(client_router, prefix='/client', tags=['client']) -app.include_router(book_router, prefix='/reference', tags=['Reference book']) +app.include_router(accounts_router) +app.include_router(book_router, prefix='/references', tags=['Reference book']) app.include_router(desk_router, prefix='/desk', tags=['Desk']) +app.include_router(dev_router, prefix='/desk', tags=['Desk']) app.include_router(users_routes) diff --git a/src/db/base.py b/src/db/base.py index 5756950..fa3440e 100644 --- a/src/db/base.py +++ b/src/db/base.py @@ -1,5 +1,5 @@ from .db import Base -from ..client_account import models +from ..accounts.client_account import models from ..reference_book import models from ..desk import models from ..users import models diff --git a/src/desk/dev_routes.py b/src/desk/dev_routes.py new file mode 100644 index 0000000..1fd9b4c --- /dev/null +++ b/src/desk/dev_routes.py @@ -0,0 +1,14 @@ +from fastapi import APIRouter, status, Depends +from typing import List + +from src.desk.schemas import AppealBase +from src.desk.services import get_all_appeals +from src.users.logic import developer_user +from src.users.models import UserTable + +dev_router = APIRouter() + + +# @dev_router.get("/", response_model=List[AppealBase], status_code=status.HTTP_200_OK) +# async def all_appeals_list(user: UserTable = Depends(developer_user)): +# return await get_all_appeals() diff --git a/src/desk/schemas.py b/src/desk/schemas.py index a31b21b..f440c40 100644 --- a/src/desk/schemas.py +++ b/src/desk/schemas.py @@ -3,7 +3,7 @@ from .models import StatusTasks from pydantic import BaseModel -from ..client_account.schemas import ClientDB +from ..accounts.client_account.schemas import ClientDB from ..reference_book.schemas import SoftwareDB, ModuleShort from ..users.schemas import UserDB diff --git a/src/desk/services.py b/src/desk/services.py index 478a7e2..bd69de1 100644 --- a/src/desk/services.py +++ b/src/desk/services.py @@ -1,5 +1,5 @@ from .schemas import AppealCreate, CommentCreate, AppealUpdate, CommentUpdate -from ..client_account.models import clients +from ..accounts.client_account.models import clients from ..db.db import database from .models import appeals, comments from ..reference_book.models import softwares, modules @@ -56,7 +56,7 @@ async def add_appeal(appeal: AppealCreate, user: UserTable): async def update_appeal(id: int, appeal: AppealUpdate, user: UserTable): query = appeals.update().\ where((appeals.c.id == id) & (appeals.c.client_id == user.client_id)).\ - values(**appeal.dict()) + values(appeal.dict()) result_id = await database.execute(query) return await get_appeal_db(result_id) diff --git a/src/reference_book/api/licence.py b/src/reference_book/api/licence.py index 251d6ef..788ff75 100644 --- a/src/reference_book/api/licence.py +++ b/src/reference_book/api/licence.py @@ -8,7 +8,7 @@ from ...users.logic import developer_user router = APIRouter() -for_client_router = APIRouter() +client_licences_router = APIRouter() @router.get("/", response_model=List[LicenceDB], status_code=status.HTTP_200_OK) @@ -21,26 +21,26 @@ async def licence(id: int, user: UserTable = Depends(developer_user)): return await get_licence(id) -@for_client_router.get("/{id}/licences", response_model=List[LicenceDB], status_code=status.HTTP_200_OK) +@client_licences_router.get("/{id}/licences", response_model=List[LicenceDB], status_code=status.HTTP_200_OK) async def client_licence_list(id: int, user: UserTable = Depends(developer_user)): return await get_client_licences(id) -@for_client_router.get('/{id}/licences/{pk}', response_model=Licence, status_code=status.HTTP_200_OK) +@client_licences_router.get('/{id}/licences/{pk}', response_model=Licence, status_code=status.HTTP_200_OK) async def client_licence(id: int, pk: int, user: UserTable = Depends(developer_user)): return await get_client_licence(id, pk) -@for_client_router.post("/{id}/licences/", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) +@client_licences_router.post("/{id}/licences/", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) async def create_client_licence(id: int, licence: LicenceCreate, user: UserTable = Depends(developer_user)): return await add_client_licence(id, licence) -@for_client_router.put("/{id}/licences/{pk}", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) +@client_licences_router.put("/{id}/licences/{pk}", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) async def update_client_licence_by_id(id: int, pk: int, item: LicenceCreate, user: UserTable = Depends(developer_user)): return await update_client_licence(id, pk, item) -@for_client_router.delete("/{id}/licences/{pk}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) +@client_licences_router.delete("/{id}/licences/{pk}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) async def delete_client_licence_by_id(id: int, pk: int, user: UserTable = Depends(developer_user)): await delete_client_licence(id, pk) diff --git a/src/reference_book/schemas.py b/src/reference_book/schemas.py index 0efa9d6..2c5e264 100644 --- a/src/reference_book/schemas.py +++ b/src/reference_book/schemas.py @@ -3,8 +3,6 @@ from pydantic import BaseModel -# from src.client_account.schemas import ClientDB - class SoftwareBase(BaseModel): name: str = '' @@ -45,7 +43,7 @@ class LicenceDB(LicenceBase): class Licence(LicenceBase): id: int - client_id: int # TODO заменить на Client + client_id: int software: SoftwareDB diff --git a/src/reference_book/services.py b/src/reference_book/services.py index b32510e..02ce8ba 100644 --- a/src/reference_book/services.py +++ b/src/reference_book/services.py @@ -1,7 +1,7 @@ from .schemas import ModuleCreate, LicenceCreate, SoftwareCreate -from ..client_account.services import get_client from ..db.db import database from .models import softwares, modules, licences +from ..accounts.client_account.services import activate_client async def get_softwares(): @@ -66,7 +66,7 @@ async def get_module_db(id: int): return dict(await database.fetch_one(modules.select().where(modules.c.id == id))) -async def add_module(id: int, module: ModuleCreate): # TODO посмотреть, что лучше работает, словарь или вызов бд +async def add_module(id: int, module: ModuleCreate): query = modules.insert().values({**module.dict(), "software_id": id}) module_id = await database.execute(query) return {"id": module_id, **module.dict(), "software_id": id} @@ -92,7 +92,6 @@ async def get_licence(id: int): result = await database.fetch_one(query=licences.select().where(licences.c.id == id)) if result is not None: licence = dict(result) - # client = await get_client(licence["client_id"]) software = await get_software(licence["software_id"]) return {**licence, "software": software} return None @@ -117,6 +116,7 @@ async def add_client_licence(id: int, licence: LicenceCreate): item = {**licence.dict(), "client_id": id} query = licences.insert().values(item) licence_id = await database.execute(query) + await activate_client(id) return {"id": licence_id, **licence.dict(), "client_id": id} From bad9b49929d101d43c900efc4621a2fa11b195f3 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Thu, 29 Apr 2021 13:36:30 +0500 Subject: [PATCH 26/55] add user handlers and errors --- src/errors.py | 4 ++++ src/users/logic.py | 53 +++++++++++++++++++++++++++++++++++++------- src/users/models.py | 2 +- src/users/schemas.py | 43 ++++++++++++++++++++++++++++------- 4 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 src/errors.py diff --git a/src/errors.py b/src/errors.py new file mode 100644 index 0000000..5952c6b --- /dev/null +++ b/src/errors.py @@ -0,0 +1,4 @@ +class Errors: + NO_VACANCIES_UNDER_LICENCES = "NO_VACANCIES_UNDER_LICENCES" + USER_NOT_FOUND = "USER_NOT_FOUND" + IMPOSSIBLE_DELETE_OWNER = "IMPOSSIBLE_DELETE_OWNER" diff --git a/src/users/logic.py b/src/users/logic.py index acf8baf..8b10966 100644 --- a/src/users/logic.py +++ b/src/users/logic.py @@ -1,11 +1,18 @@ -from fastapi import HTTPException, Depends, status -from fastapi_users.authentication import JWTAuthentication +from fastapi import HTTPException, Depends, status, Request from fastapi_users import FastAPIUsers -from requests import Request +from fastapi_users.authentication import JWTAuthentication +from fastapi_users.password import get_password_hash from ..config import SECRET from .schemas import User, UserCreate, UserUpdate, UserDB -from .models import user_db, UserTable +from .models import user_db, UserTable, users +from src.db.db import database +from src.errors import Errors + +from typing import Dict, Any +from pydantic.types import UUID4 +# from requests import Request + auth_backends = [] @@ -27,14 +34,14 @@ developer_user = all_users.current_user(active=True, superuser=True) -async def get_owner(user: UserTable = Depends(any_user)): - if not user.is_owner: +async def get_owner(client_id: int, user: UserTable = Depends(any_user)): + if not (user.client_id == client_id and user.is_owner): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) return user -async def get_owner_with_superuser(user: UserTable = Depends(any_user)): - if not user.is_superuser and not user.is_owner: +async def get_owner_with_superuser(client_id: int, user: UserTable = Depends(any_user)): + if not user.is_superuser and not (user.client_id == client_id and user.is_owner): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) return user @@ -65,3 +72,33 @@ async def after_verification_request(user: UserDB, token: str, request: Request) async def after_verification(user: UserDB, request: Request): print(f"{user.id} is now verified.") + + +async def get_or_404(id: UUID4) -> UserDB: + user = await database.fetch_one(query=users.select().where(users.c.id == id)) + if user is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.USER_NOT_FOUND) + return user + + +async def update_user(id: UUID4, update_dict: Dict[str, Any]): + user = await get_or_404(id) + user = {**user} + for field in update_dict: + if field == "password": + hashed_password = get_password_hash(update_dict[field]) + user["hashed_password"] = hashed_password + else: + user[field] = update_dict[field] + + updated_user = await user_db.update(UserDB(**user)) + + return updated_user + + +async def delete_user(id: UUID4): + user = await get_or_404(id) + await user_db.delete(user) + return None diff --git a/src/users/models.py b/src/users/models.py index 2d583fd..ccf228a 100644 --- a/src/users/models.py +++ b/src/users/models.py @@ -12,7 +12,7 @@ class UserTable(Base, SQLAlchemyBaseUserTable): surname = Column(String, nullable=False) patronymic = Column(String, nullable=True) # avatar - is_owner = Column(Boolean, default=False, nullable=False) + is_owner = Column(Boolean, default=False, nullable=True) client_id = Column(Integer, ForeignKey('client.id')) # TODO сделать проверку на существующего владельца клиента date_reg = Column(DateTime(timezone=True), server_default=sql.func.now()) date_block = Column(DateTime, default=None, nullable=True) diff --git a/src/users/schemas.py b/src/users/schemas.py index d7a0033..b34ea2d 100644 --- a/src/users/schemas.py +++ b/src/users/schemas.py @@ -9,19 +9,19 @@ class User(models.BaseUser): name: str surname: str patronymic: Optional[str] - is_owner: bool - client_id: int - date_block: Optional[datetime] + # is_owner: bool + # client_id: int + # date_block: Optional[datetime] class UserCreate(models.BaseUserCreate): name: str surname: str patronymic: Optional[str] - is_owner: bool = False - client_id: int - date_reg: datetime # TODO попробовать удалить данное поле из регистрации. Пока без нее не работет - date_block: Optional[datetime] + # is_owner: bool = False + # client_id: int + # date_reg: datetime # TODO попробовать удалить данное поле из регистрации. Пока без нее не работет + # date_block: Optional[datetime] @validator('password') def valid_password(cls, v: str): @@ -30,9 +30,36 @@ def valid_password(cls, v: str): return v +class EmployeeCreate(UserCreate, models.BaseUserCreate): + # avatar + is_owner: bool = False + client_id: int + date_reg: datetime + + +class DeveloperCreate(UserCreate, models.BaseUserCreate): + # avatar + date_reg: datetime + + +class OwnerCreate(UserCreate, models.BaseUserCreate): + # avatar + is_owner: bool = True + client_id: int + date_reg: datetime + + class UserUpdate(User, models.BaseUserUpdate): - pass + name: Optional[str] + surname: Optional[str] + is_owner: Optional[bool] + is_active: Optional[bool] + date_block: Optional[datetime] class UserDB(User, models.BaseUserDB): + is_active: bool + is_owner: Optional[bool] + client_id: Optional[int] date_reg: datetime + date_block: Optional[datetime] From 673fa2b80c2e5ff4c6ecb40af9aa6fb2a0c0e958 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Thu, 29 Apr 2021 13:37:20 +0500 Subject: [PATCH 27/55] delete old client account --- src/client_account/routers.py | 35 -------------------------------- src/client_account/services.py | 37 ---------------------------------- 2 files changed, 72 deletions(-) delete mode 100644 src/client_account/routers.py delete mode 100644 src/client_account/services.py diff --git a/src/client_account/routers.py b/src/client_account/routers.py deleted file mode 100644 index 98c878d..0000000 --- a/src/client_account/routers.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import List - -from fastapi import APIRouter, Depends, status -from .schemas import ClientDB, Client, ClientCreate, ClientUpdate -from .services import get_clients, get_client, add_client -from ..users.models import UserTable -from ..users.logic import developer_user, any_user, get_client_users_with_superuser, get_owner -from src.reference_book.api.licence import for_client_router - -router = APIRouter() - - -@router.get("/", response_model=List[ClientDB], status_code=status.HTTP_200_OK) -async def clients_list(user: UserTable = Depends(developer_user)): - return await get_clients() - - -@router.post("/", response_model=ClientDB, status_code=status.HTTP_201_CREATED) -async def create_client(item: ClientCreate, user: UserTable = Depends(developer_user)): - return await add_client(item) - - -@router.get("/{id}", response_model=Client, status_code=status.HTTP_200_OK) -async def client(id: int, user: UserTable = Depends(any_user)): - user = await get_client_users_with_superuser(id, user) - return await get_client(id) - - -# @router.put("/{id}", response_model=Client, status_code=status.HTTP_201_CREATED) -# async def client(id: int, item: ClientUpdate, user: UserTable = Depends(get_owner)): -# user = get_client_users_with_superuser(id, user) -# return await update_client(id, item) - - -router.include_router(for_client_router) diff --git a/src/client_account/services.py b/src/client_account/services.py deleted file mode 100644 index 37bb5f3..0000000 --- a/src/client_account/services.py +++ /dev/null @@ -1,37 +0,0 @@ -from .schemas import ClientCreate -from ..db.db import database -from .models import clients -from ..reference_book.models import licences -from ..users.models import users, UserTable - - -async def get_clients(): - result = await database.fetch_all(clients.select()) - return [dict(client) for client in result] - - -async def get_client(id: int): - query = clients.select().where(clients.c.id == id) - result = await database.fetch_one(query=query) - if result: - result = dict(result) - owner = dict(await database.fetch_one(query=users.select().where(users.c.id == result["owner_id"]))) - employee_data = await database.fetch_all(query=users.select().where(users.c.client_id == id)) - employees = [dict(employee) for employee in employee_data] - licence_data = await database.fetch_all(query=licences.select().where(licences.c.client_id == id)) - licences_dict = [dict(licence) for licence in licence_data] - return {**result, - "owner": owner, - "employees": employees, - "licences": licences_dict} - - -async def get_client_db(client_id): - query = clients.select().where(clients.c.id == client_id) - return dict(await database.fetch_one(query=query)) - - -async def add_client(client: ClientCreate): - query = clients.insert().values(client.dict()) - client_id = await database.execute(query) - return await get_client_db(client_id) From 0cdb9c5c752fb50ecd5b55c9131e3db8bf7aa763 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Thu, 29 Apr 2021 21:05:13 +0500 Subject: [PATCH 28/55] update CRUD methods for desk --- src/accounts/client_account/services.py | 13 +-- src/accounts/developer_account/routers.py | 4 +- src/accounts/developer_account/services.py | 7 +- src/desk/models.py | 7 +- src/desk/routes.py | 53 ++++++--- src/desk/schemas.py | 25 +++-- src/desk/services.py | 124 +++++++++++++++------ src/errors.py | 2 + src/service.py | 6 + src/users/logic.py | 8 ++ 10 files changed, 167 insertions(+), 82 deletions(-) create mode 100644 src/service.py diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index b163779..a6b4e4e 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -1,6 +1,6 @@ from datetime import datetime -from fastapi import HTTPException, status, Request +from fastapi import HTTPException, status from fastapi.responses import RedirectResponse from fastapi_users.router import ErrorCode from pydantic.types import UUID4 @@ -12,14 +12,9 @@ from ...errors import Errors from ...reference_book.models import licences, softwares from ...users.logic import all_users, get_or_404 -from ...users.models import users, user_db +from ...users.models import users from ...users.schemas import UserCreate, OwnerCreate, UserUpdate - - -async def check_and_convert_to_dict(result): - if result: - return dict(result) - return [] +from ...service import check_dict async def get_clients(): @@ -33,7 +28,7 @@ async def get_client(id: int): if result: result = dict(result) owner_data = await database.fetch_one(query=users.select().where(users.c.id == result["owner_id"])) - owner = await check_and_convert_to_dict(owner_data) + owner = await check_dict(owner_data) employee_data = await database.fetch_all(query=users.select().where(users.c.client_id == id)) employees = [dict(employee) for employee in employee_data] licence_data = await database.fetch_all(query=licences.select().where(licences.c.client_id == id)) diff --git a/src/accounts/developer_account/routers.py b/src/accounts/developer_account/routers.py index a1f2b9d..ab53179 100644 --- a/src/accounts/developer_account/routers.py +++ b/src/accounts/developer_account/routers.py @@ -3,9 +3,9 @@ from fastapi import APIRouter, Depends, status, Response from pydantic.types import UUID4 -from .services import get_developers, get_developer, add_developer, update_developer, delete_developer +from .services import get_developer, add_developer, update_developer, delete_developer from src.users.models import UserTable -from src.users.logic import developer_user +from src.users.logic import developer_user, get_developers from src.users.schemas import UserDB, UserCreate, UserUpdate developer_router = APIRouter() diff --git a/src/accounts/developer_account/services.py b/src/accounts/developer_account/services.py index 77f9194..38d247f 100644 --- a/src/accounts/developer_account/services.py +++ b/src/accounts/developer_account/services.py @@ -1,6 +1,6 @@ from datetime import datetime -from fastapi import Request, HTTPException, status +from fastapi import HTTPException, status from fastapi_users.router import ErrorCode from pydantic.types import UUID4 @@ -10,11 +10,6 @@ from src.users.schemas import UserCreate, DeveloperCreate, UserUpdate -async def get_developers(): - developers_data = await database.fetch_all(users.select().where(users.c.is_superuser is True)) - return [dict(developer) for developer in developers_data] - - async def get_developer(id: UUID4): developer = await database.fetch_one(users.select().where((users.c.is_superuser is True) & (users.c.id == id))) if developer: diff --git a/src/desk/models.py b/src/desk/models.py index 31e6f0f..b5460f5 100644 --- a/src/desk/models.py +++ b/src/desk/models.py @@ -1,4 +1,5 @@ import enum + from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, sql, Enum from ..db.db import Base @@ -6,7 +7,7 @@ class StatusTasks(enum.Enum): new = "New" - register = "Registered" + registered = "Registered" in_work = "In work" closed = "Closed" canceled = "Canceled" @@ -21,10 +22,10 @@ class Appeal(Base): text = Column(String(500), nullable=False) client_id = Column(Integer, ForeignKey('client.id')) author_id = Column(String, ForeignKey('user.id')) + responsible_id = Column(String, ForeignKey('user.id'), nullable=True) + status = Column(Enum(StatusTasks), default=StatusTasks.new) date_create = Column(DateTime(timezone=True), server_default=sql.func.now()) date_processing = Column(DateTime, default=None) - status = Column(Enum(StatusTasks), default=StatusTasks.new) - responsible_id = Column(String, ForeignKey('user.id'), nullable=True) software_id = Column(Integer, ForeignKey('software.id'), nullable=False) module_id = Column(Integer, ForeignKey('module.id'), nullable=False) # attachments diff --git a/src/desk/routes.py b/src/desk/routes.py index 618f1fd..6a07437 100644 --- a/src/desk/routes.py +++ b/src/desk/routes.py @@ -1,57 +1,74 @@ -from fastapi import APIRouter, status, Depends +from fastapi import APIRouter, status, Depends, Response from typing import List -from .services import get_appeals, get_appeal, get_comments, get_comment, \ - add_appeal, add_comment, update_appeal, update_comment, delete_comment +from pydantic.types import UUID4 + +from .services import get_all_appeals, get_appeals, get_appeal, get_comments, get_comment, \ + add_appeal, add_comment, update_appeal, update_comment, delete_comment, update_attachments from .schemas import Appeal, CommentShort, Comment, AppealCreate, CommentCreate, CommentDB, \ - AppealUpdate, AppealDB, AppealShort, CommentUpdate + AppealUpdate, AppealDB, AppealShort, CommentUpdate, DevAppeal from ..users.models import UserTable -from ..users.logic import employee +from ..users.logic import employee, any_user, developer_user router = APIRouter() @router.get("/", response_model=List[AppealShort], status_code=status.HTTP_200_OK) -async def appeals_list(user: UserTable = Depends(employee)): +async def appeals_list(user: UserTable = Depends(any_user)): + if user.is_superuser: + return await get_all_appeals() return await get_appeals(user) -@router.get("/{id}", response_model=Appeal, status_code=status.HTTP_200_OK) +@router.get("/{id}", status_code=status.HTTP_200_OK) async def appeal(id: int, user: UserTable = Depends(employee)): return await get_appeal(id, user) +# @router.get("/{id}", response_model=DevAppeal, status_code=status.HTTP_200_OK) +# async def appeal(id: int, user: UserTable = Depends(developer_user)): +# print("WINDOW OF DEVELOPER") +# print(user.is_superuser) +# return await get_appeal(id, user) + + @router.post("/", response_model=AppealDB, status_code=status.HTTP_201_CREATED) async def create_appeal(item: AppealCreate, user: UserTable = Depends(employee)): return await add_appeal(item, user) -@router.put("/{id}", response_model=AppealDB, status_code=status.HTTP_201_CREATED) -async def update_appeal_by_id(id: int, item: AppealUpdate, user: UserTable = Depends(employee)): +# @router.patch("/{id}", status_code=status.HTTP_201_CREATED) +# async def update_attachments_on_appeal(id: int, item: AppealUpdate, user: UserTable = Depends(employee)): +# pass + # return await update_attachments(id, item, user) + + +@router.patch("/{id}", response_model=AppealDB, status_code=status.HTTP_201_CREATED) +async def update_appeal_by_id(id: int, item: AppealUpdate, user: UserTable = Depends(developer_user)): return await update_appeal(id, item, user) @router.get("/{id}/comments", response_model=List[CommentShort], status_code=status.HTTP_200_OK) -async def comments_list(id: int, user: UserTable = Depends(employee)): +async def comments_list(id: int, user: UserTable = Depends(any_user)): return await get_comments(id, user) @router.get("/{id}/comments/{pk}", response_model=Comment, status_code=status.HTTP_200_OK) -async def comment(id: int, pk: int, user: UserTable = Depends(employee)): +async def comment(id: int, pk: int, user: UserTable = Depends(any_user)): return await get_comment(id, pk, user) @router.post("/{id}/comments/", response_model=CommentDB, status_code=status.HTTP_201_CREATED) -async def create_comment(id: int, item: CommentCreate, user: UserTable = Depends(employee)): +async def create_comment(id: int, item: CommentCreate, user: UserTable = Depends(any_user)): return await add_comment(id, item, user) -@router.put("/{id}/comments/{pk}", response_model=CommentDB, status_code=status.HTTP_201_CREATED) -async def update_comment_by_id(id: int, item: CommentUpdate, user: UserTable = Depends(employee)): - return await update_comment(id, item, user) +@router.patch("/{id}/comments/{pk}", response_model=CommentDB, status_code=status.HTTP_201_CREATED) +async def update_comment_by_id(id: int, pk: int, item: CommentUpdate, user: UserTable = Depends(any_user)): + return await update_comment(id, pk, item, user) -@router.delete("/{id}/comments/{pk}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_comment_by_id(id: int, user: UserTable = Depends(employee)): - return await delete_comment(id, user) +@router.delete("/{id}/comments/{pk}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) +async def delete_comment_by_id(id: int, pk: int, user: UserTable = Depends(employee)): + await delete_comment(id, pk, user) diff --git a/src/desk/schemas.py b/src/desk/schemas.py index f440c40..682b51e 100644 --- a/src/desk/schemas.py +++ b/src/desk/schemas.py @@ -1,5 +1,6 @@ from typing import Optional, List from datetime import datetime + from .models import StatusTasks from pydantic import BaseModel @@ -18,8 +19,13 @@ class AppealCreate(AppealBase): module_id: int -class AppealUpdate(AppealCreate): - pass +class AppealUpdate(AppealBase): + topic: Optional[str] + text: Optional[str] + status: Optional[StatusTasks] + software_id: Optional[int] + module_id: Optional[int] + responsible_id: Optional[str] class AppealShort(AppealBase): @@ -27,14 +33,14 @@ class AppealShort(AppealBase): status: StatusTasks -class AppealDB(AppealCreate): +class AppealDB(AppealBase): id: int client_id: int author_id: str status: StatusTasks date_create: datetime date_processing: Optional[datetime] - responsible_id: Optional[int] + responsible_id: Optional[str] software_id: int module_id: int @@ -73,12 +79,17 @@ class CommentShort(CommentBase): class Appeal(AppealBase): id: int - status: StatusTasks - date_create: datetime - date_processing: Optional[datetime] = None client: ClientDB author: UserDB responsible: Optional[UserDB] + status: StatusTasks + date_create: datetime + date_processing: Optional[datetime] software: SoftwareDB module: ModuleShort comment: Optional[List[CommentShort]] + + +class DevAppeal(Appeal): + developers: List[UserDB] + allowed_statuses: List[StatusTasks] diff --git a/src/desk/services.py b/src/desk/services.py index bd69de1..eab689c 100644 --- a/src/desk/services.py +++ b/src/desk/services.py @@ -1,10 +1,17 @@ +from datetime import datetime + from .schemas import AppealCreate, CommentCreate, AppealUpdate, CommentUpdate from ..accounts.client_account.models import clients from ..db.db import database from .models import appeals, comments +from ..errors import Errors from ..reference_book.models import softwares, modules +from ..users.logic import get_developers from ..users.models import UserTable, users from .models import StatusTasks +from ..service import check_dict + +from fastapi import HTTPException, status async def get_all_appeals(): @@ -19,44 +26,64 @@ async def get_appeals(user: UserTable): async def get_appeal(id: int, user: UserTable): - query = appeals.select().where((appeals.c.id == id) & (appeals.c.client_id == user.client_id)) - appeal = await database.fetch_one(query=query) - if appeal: - appeal = dict(appeal) - # TODO сделать проверку на None - client = dict(await database.fetch_one(query=clients.select().where(clients.c.id == appeal["client_id"]))) - author = dict(await database.fetch_one(query=users.select().where(users.c.id == appeal["author_id"]))) - responsible = await database.fetch_one(query=users.select().where(users.c.id == appeal["responsible_id"])) - software = dict(await database.fetch_one(query=softwares.select().where(softwares.c.id == appeal["software_id"]))) - module = dict(await database.fetch_one(query=modules.select().where(modules.c.id == appeal["module_id"]))) - comment = await get_comments(id, user) - return {**appeal, - "client": client, - "author": author, - "responsible": responsible, - "software": software, - "module": module, - "comment": comment} - return None + appeal = await check_access(id, user, status.HTTP_404_NOT_FOUND) + client = await check_dict(await database.fetch_one(query=clients.select().where(clients.c.id == appeal["client_id"]))) + author = await check_dict(await database.fetch_one(query=users.select().where(users.c.id == appeal["author_id"]))) + responsible = await check_dict( + await database.fetch_one(query=users.select().where(users.c.id == appeal["responsible_id"]))) + software = await check_dict( + await database.fetch_one(query=softwares.select().where(softwares.c.id == appeal["software_id"]))) + module = await check_dict(await database.fetch_one(query=modules.select().where(modules.c.id == appeal["module_id"]))) + comment = await get_comments(id, user) + result = {**appeal, + "client": client, + "author": author, + "responsible": responsible, + "software": software, + "module": module, + "comment": comment} + if user.is_superuser: + developers = await get_developers() + allowed_statuses = await get_next_status(id, user) + return {**result, + "developers": developers, + "allowed_statuses": allowed_statuses} + return result async def get_appeal_db(id: int): query = appeals.select().where(appeals.c.id == id) result = await database.fetch_one(query=query) - return dict(result) + if result: + return dict(result) + else: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) async def add_appeal(appeal: AppealCreate, user: UserTable): - item = {**appeal.dict(), "client_id": int(user.client_id), "author_id": str(user.id), "status": StatusTasks.new} # TODO убрать статус, после того, как настрою default + item = {**appeal.dict(), "client_id": int(user.client_id), "author_id": str(user.id), "status": StatusTasks.new} query = appeals.insert().values(item) appeal_id = await database.execute(query) return await get_appeal_db(appeal_id) +async def update_attachments(id: int, appeal: AppealUpdate, user: UserTable): + # TODO сделать добавление вложений для пользователя + pass + + async def update_appeal(id: int, appeal: AppealUpdate, user: UserTable): - query = appeals.update().\ - where((appeals.c.id == id) & (appeals.c.client_id == user.client_id)).\ - values(appeal.dict()) + old_appeal = await get_appeal_db(id) + if old_appeal["status"] == StatusTasks.closed or old_appeal["status"] == StatusTasks.canceled: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=Errors.APPEAL_IS_CLOSED) + appeal = appeal.dict(exclude_unset=True) + if "status" in appeal and (appeal["status"] == StatusTasks.closed or appeal["status"] == StatusTasks.canceled): + appeal["date_processing"] = datetime.utcnow() + if "responsible_id" in appeal and old_appeal["status"] == StatusTasks.new: + appeal["status"] = StatusTasks.registered + for field in appeal: + old_appeal[field] = appeal[field] + query = appeals.update().where(appeals.c.id == id).values(old_appeal) result_id = await database.execute(query) return await get_appeal_db(result_id) @@ -68,15 +95,14 @@ async def delete_appeal(id: int): async def get_comments(id: int, user: UserTable): - # TODO проверка на пользователя имеющего доступ: (appeals.c.client_id == user.client_id) + await check_access(id, user, status.HTTP_404_NOT_FOUND) query = comments.select().where(comments.c.appeal_id == id) result = await database.fetch_all(query) - result = [dict(comment) for comment in result] - return result + return [dict(comment) for comment in result] async def get_comment(id: int, pk: int, user: UserTable): - # TODO проверка на пользователя имеющего доступ: (appeals.c.client_id == user.client_id) + await check_access(id, user, status.HTTP_404_NOT_FOUND) query = comments.select().where((comments.c.id == pk) & (comments.c.appeal_id == id)) comment = await database.fetch_one(query) if comment: @@ -88,25 +114,49 @@ async def get_comment(id: int, pk: int, user: UserTable): async def get_comment_db(id: int): query = comments.select().where(comments.c.id == id) - return dict(await database.fetch_one(query=query)) + result = await database.fetch_one(query=query) + return await check_dict(result) async def add_comment(appeal_id: int, comment: CommentCreate, user: UserTable): - # TODO проверка на пользователя имеющего доступ: (appeals.c.client_id == user.client_id) + await check_access(appeal_id, user, status.HTTP_403_FORBIDDEN) item = {**comment.dict(), "appeal_id": int(appeal_id), "author_id": str(user.id)} query = comments.insert().values(item) comment_id = await database.execute(query) return await get_comment_db(comment_id) -async def update_comment(id: int, comment: CommentUpdate, user: UserTable): - query = comments.update().where((comments.c.id == id) & (comments.c.author_id == user.id)).values(**comment.dict()) +async def update_comment(appeal_id: int, pk: int, comment: CommentUpdate, user: UserTable): + await check_access(appeal_id, user, status.HTTP_403_FORBIDDEN) + query = comments.update().where((comments.c.id == pk) & (comments.c.author_id == user.id)).values(**comment.dict()) result_id = await database.execute(query) return await get_comment_db(result_id) -async def delete_comment(id: int, user: UserTable): - query = comments.delete().where((comments.c.id == id) & (comments.c.author_id == str(user.id))) - result = await database.execute(query) - print(result) - return result +async def delete_comment(appeal_id: int, pk: int, user: UserTable): + await check_access(appeal_id, user, status.HTTP_403_FORBIDDEN) + query = comments.delete().where((comments.c.id == pk) & (comments.c.author_id == str(user.id))) + await database.execute(query) + + +async def check_access(appeal_id: int, user: UserTable, status_code: status): + appeal = await get_appeal_db(appeal_id) + if appeal["client_id"] != user.client_id and not user.is_superuser: + raise HTTPException(status_code=status_code) + return appeal + + +async def get_next_status(appeal_id: int, user: UserTable): + appeal = await get_appeal_db(appeal_id) + current_status = appeal["status"] + if user.is_superuser: + if current_status is StatusTasks.new: + return [StatusTasks.registered] + elif current_status is StatusTasks.registered: + return [StatusTasks.in_work] + elif current_status is StatusTasks.in_work: + return [StatusTasks.closed, StatusTasks.canceled] + elif current_status is StatusTasks.closed or current_status is StatusTasks.canceled: + return [StatusTasks.in_work] + else: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=Errors.USER_CAN_NOT_CHANGE_STATUS) diff --git a/src/errors.py b/src/errors.py index 5952c6b..8d48ba2 100644 --- a/src/errors.py +++ b/src/errors.py @@ -2,3 +2,5 @@ class Errors: NO_VACANCIES_UNDER_LICENCES = "NO_VACANCIES_UNDER_LICENCES" USER_NOT_FOUND = "USER_NOT_FOUND" IMPOSSIBLE_DELETE_OWNER = "IMPOSSIBLE_DELETE_OWNER" + USER_CAN_NOT_CHANGE_STATUS = "USER_CAN_NOT_CHANGE_STATUS_ON_THE_NEXT" + APPEAL_IS_CLOSED = "APPEAL_IS_CLOSED" diff --git a/src/service.py b/src/service.py new file mode 100644 index 0000000..f98bfba --- /dev/null +++ b/src/service.py @@ -0,0 +1,6 @@ + + +async def check_dict(result): + if result: + return dict(result) + return None diff --git a/src/users/logic.py b/src/users/logic.py index 8b10966..a8f9b76 100644 --- a/src/users/logic.py +++ b/src/users/logic.py @@ -74,6 +74,14 @@ async def after_verification(user: UserDB, request: Request): print(f"{user.id} is now verified.") +async def get_developers(): + query = users.select().where(users.c.is_superuser is True) + result = await database.fetch_all(query=query) + if result: + return [dict(developer) for developer in result] + return [] + + async def get_or_404(id: UUID4) -> UserDB: user = await database.fetch_one(query=users.select().where(users.c.id == id)) if user is None: From 4beae65bd0056bf8bb6309ba50c8c1e1d10ee8b0 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Fri, 30 Apr 2021 16:45:16 +0500 Subject: [PATCH 29/55] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20CRUD=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D1=8B=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D0=BB=D0=B8=D1=86?= =?UTF-8?q?=D0=B5=D0=BD=D0=B7=D0=B8=D1=8F=D0=BC=D0=B8=20=D0=B2=20=D1=81?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BE=D1=87=D0=BD=D0=B8=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/reference_book/api/licence.py | 18 +++++++++++++++++- src/reference_book/schemas.py | 1 + src/reference_book/services.py | 19 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/reference_book/api/licence.py b/src/reference_book/api/licence.py index 788ff75..d767026 100644 --- a/src/reference_book/api/licence.py +++ b/src/reference_book/api/licence.py @@ -3,7 +3,8 @@ from fastapi import APIRouter, Depends, status, Response from ..schemas import Licence, LicenceCreate, LicenceDB from ..services import get_licence, get_licences, \ - get_client_licences, get_client_licence, add_client_licence, update_client_licence, delete_client_licence + get_client_licences, get_client_licence, add_client_licence, update_client_licence, delete_client_licence, \ + add_licence, update_licence, delete_licence from ...users.models import UserTable from ...users.logic import developer_user @@ -21,6 +22,21 @@ async def licence(id: int, user: UserTable = Depends(developer_user)): return await get_licence(id) +@router.post("/", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) +async def create_licence(licence: LicenceCreate, user: UserTable = Depends(developer_user)): + return await add_licence(licence) + + +@router.put("/{id}", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) +async def update_licence_by_id(id: int, item: LicenceCreate, user: UserTable = Depends(developer_user)): + return await update_licence(id, item) + + +@router.delete("/{id}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) +async def delete_licence_by_id(id: int, user: UserTable = Depends(developer_user)): + await delete_licence(id) + + @client_licences_router.get("/{id}/licences", response_model=List[LicenceDB], status_code=status.HTTP_200_OK) async def client_licence_list(id: int, user: UserTable = Depends(developer_user)): return await get_client_licences(id) diff --git a/src/reference_book/schemas.py b/src/reference_book/schemas.py index 2c5e264..3fbf37c 100644 --- a/src/reference_book/schemas.py +++ b/src/reference_book/schemas.py @@ -26,6 +26,7 @@ class LicenceBase(BaseModel): class LicenceCreate(LicenceBase): + client_id: int software_id: int diff --git a/src/reference_book/services.py b/src/reference_book/services.py index 02ce8ba..2777b5c 100644 --- a/src/reference_book/services.py +++ b/src/reference_book/services.py @@ -112,6 +112,25 @@ async def get_client_licence(id: int, pk: int): return None +async def add_licence(licence: LicenceCreate): + item = {**licence.dict()} + query = licences.insert().values(item) + licence_id = await database.execute(query) + await activate_client(item["client_id"]) + return {"id": licence_id, **licence.dict()} + + +async def update_licence(pk: int, licence: LicenceCreate): + query = licences.update().where(licences.c.id == pk).values(**licence.dict()) + await database.execute(query) + return {"id": pk, **licence.dict()} + + +async def delete_licence(pk: int): + query = licences.delete().where(licences.c.id == pk) + await database.execute(query) + + async def add_client_licence(id: int, licence: LicenceCreate): item = {**licence.dict(), "client_id": id} query = licences.insert().values(item) From 6df9952920583f497a9635a3901647dc709826ea Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Fri, 30 Apr 2021 16:45:59 +0500 Subject: [PATCH 30/55] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B8=D1=80=D0=B5=D0=BA=D1=82=D1=8B,=20=D1=82?= =?UTF-8?q?=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=20=D0=B8=20=D0=B2=D0=BB=D0=B0=D0=B4=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D1=86=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D1=8E=D1=82=D1=81=D1=8F?= =?UTF-8?q?=20=D0=BE=D0=B4=D0=BD=D0=BE=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/client_account/schemas.py | 19 ++++++++++++++++++- src/accounts/client_account/services.py | 20 +++++++++++--------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/accounts/client_account/schemas.py b/src/accounts/client_account/schemas.py index 002abcf..46f7aa9 100644 --- a/src/accounts/client_account/schemas.py +++ b/src/accounts/client_account/schemas.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import Optional, List -from pydantic import BaseModel +from pydantic import BaseModel, EmailStr, validator from pydantic.types import UUID4 from ...reference_book.schemas import LicenceDB, SoftwareDB @@ -16,6 +16,23 @@ class ClientCreate(ClientBase): pass +class ClientAndOwnerCreate(ClientCreate): + owner_name: str + surname: str + patronymic: Optional[str] + email: EmailStr + password: str + is_active: Optional[bool] = True + is_superuser: Optional[bool] = False + is_verified: Optional[bool] = False + + @validator('password') + def valid_password(cls, v: str): + if len(v) < 6: + raise ValueError('Password should be at least 6 characters') + return v + + class ClientUpdate(ClientCreate): # TODO доработать изменение заказчика owner_id: Optional[UUID4] # avatar diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index a6b4e4e..72ab510 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -1,11 +1,10 @@ from datetime import datetime from fastapi import HTTPException, status -from fastapi.responses import RedirectResponse from fastapi_users.router import ErrorCode from pydantic.types import UUID4 -from .schemas import ClientCreate, ClientUpdate +from .schemas import ClientCreate, ClientUpdate, ClientAndOwnerCreate from ..employee_account.services import update_employee from ...db.db import database from .models import clients @@ -48,10 +47,13 @@ async def get_client_db(client_id): return dict(await database.fetch_one(query=query)) -async def add_client(client: ClientCreate): +async def add_client(data: ClientAndOwnerCreate): + client = ClientCreate(**data.dict()) query = clients.insert().values({**client.dict(), "is_active": False, "owner_id": "undefined"}) client_id = await database.execute(query) - return RedirectResponse(f"http://0.0.0.0:8000/clients/{client_id}/owner") + owner = OwnerCreate(**data.dict()) + await add_owner(client_id, owner) + return get_client_db(client_id) async def update_client(id: int, client: ClientUpdate): # TODO проверить работу обновления @@ -85,15 +87,16 @@ async def update_owner(client_id: int, new_owner_id: UUID4): if client: client = dict(client) if client["owner_id"] == "undefined": - client["owner_id"] = new_owner_id + new_client = ClientUpdate(**client, owner_id=new_owner_id) + await update_client(client_id, new_client) new_owner = get_or_404(new_owner_id) else: update_old = UserUpdate(is_owner=False) update_new = UserUpdate(is_owner=True) new_client = ClientUpdate(**client, owner_id=new_owner_id) - await update_employee(client["owner_id"], update_old) + old_owner = await update_employee(client["owner_id"], update_old) new_owner = await update_employee(new_owner_id, update_new) - await update_client(client_id, new_client) + client = await update_client(client_id, new_client) return new_owner @@ -110,5 +113,4 @@ async def add_owner(client_id: int, user: UserCreate): status_code=status.HTTP_400_BAD_REQUEST, detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, ) - await update_owner(client_id, created_owner.id) - return RedirectResponse(f"http://0.0.0.0:8000/clients/{client_id}") + return await update_owner(client_id, created_owner.id) From f1ca8cf24c08d0e7d291c21571cc821a5b12a0bc Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sat, 1 May 2021 22:26:44 +0500 Subject: [PATCH 31/55] fix create clients and appeals --- src/accounts/client_account/models.py | 1 + src/accounts/client_account/routers.py | 13 +++--- src/accounts/client_account/schemas.py | 9 ++-- src/accounts/client_account/services.py | 54 ++++++++++++++-------- src/accounts/developer_account/services.py | 1 + src/accounts/employee_account/routers.py | 2 +- src/accounts/employee_account/services.py | 2 +- src/desk/routes.py | 1 + src/desk/services.py | 2 +- src/users/schemas.py | 12 ++--- 10 files changed, 60 insertions(+), 37 deletions(-) diff --git a/src/accounts/client_account/models.py b/src/accounts/client_account/models.py index 3ff1546..bf98317 100644 --- a/src/accounts/client_account/models.py +++ b/src/accounts/client_account/models.py @@ -1,3 +1,4 @@ +from fastapi_users.db.sqlalchemy import GUID from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, sql from ...db.db import Base diff --git a/src/accounts/client_account/routers.py b/src/accounts/client_account/routers.py index 61f446f..735635b 100644 --- a/src/accounts/client_account/routers.py +++ b/src/accounts/client_account/routers.py @@ -1,7 +1,7 @@ from typing import List from fastapi import APIRouter, Depends, status, Request -from .schemas import ClientDB, Client, ClientCreate, ClientUpdate +from .schemas import ClientDB, Client, ClientCreate, ClientUpdate, ClientAndOwnerCreate from .services import get_clients, get_client, add_client, update_client, add_owner, get_client_owner, update_owner from src.users.models import UserTable from src.users.logic import developer_user, any_user, get_client_users_with_superuser, get_owner_with_superuser @@ -18,8 +18,9 @@ async def clients_list(user: UserTable = Depends(developer_user)): @client_router.post("/", response_model=ClientDB, status_code=status.HTTP_201_CREATED) -async def create_client(item: ClientCreate, user: UserTable = Depends(developer_user)): - return await add_client(item) +async def create_client(item: ClientAndOwnerCreate, user: UserTable = Depends(developer_user)): + new_client = await add_client(item) + return new_client @client_router.get("/{id}", response_model=Client, status_code=status.HTTP_200_OK) @@ -28,10 +29,10 @@ async def client(id: int, user: UserTable = Depends(any_user)): return await get_client(id) -@client_router.put("/{id}", response_model=ClientDB, status_code=status.HTTP_201_CREATED) +@client_router.patch("/{id}", response_model=ClientDB, status_code=status.HTTP_201_CREATED) async def update_client_by_id(id: int, item: ClientUpdate, user: UserTable = Depends(any_user)): # TODO разделить изменение аватарки владельцем и изменение владельца владельцем или разработчиком - user = get_owner_with_superuser(id, user) + user = await get_owner_with_superuser(id, user) return await update_client(id, item) @@ -48,7 +49,7 @@ async def create_owner(id: int, item: UserCreate, user: UserTable = Depends(deve @client_router.patch("/{id}/owner", status_code=status.HTTP_201_CREATED) async def change_owner(id: int, item: ClientUpdate, user: UserTable = Depends(developer_user)): - user = get_owner_with_superuser(id, user) + user = await get_owner_with_superuser(id, user) return await update_owner(id, item.owner_id) diff --git a/src/accounts/client_account/schemas.py b/src/accounts/client_account/schemas.py index 46f7aa9..139fbf2 100644 --- a/src/accounts/client_account/schemas.py +++ b/src/accounts/client_account/schemas.py @@ -13,7 +13,8 @@ class ClientBase(BaseModel): class ClientCreate(ClientBase): - pass + name: Optional[str] + owner_id: Optional[str] class ClientAndOwnerCreate(ClientCreate): @@ -33,8 +34,8 @@ def valid_password(cls, v: str): return v -class ClientUpdate(ClientCreate): # TODO доработать изменение заказчика - owner_id: Optional[UUID4] +class ClientUpdate(ClientBase): # TODO доработать изменение заказчика + name: Optional[str] # avatar pass @@ -55,4 +56,4 @@ class ClientDB(ClientBase): is_active: bool date_create: datetime date_block: Optional[datetime] - owner_id: UUID4 + owner_id: str diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index 72ab510..540a01a 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -1,4 +1,6 @@ +import uuid from datetime import datetime +from typing import Dict, Any from fastapi import HTTPException, status from fastapi_users.router import ErrorCode @@ -42,24 +44,41 @@ async def get_client(id: int): "software": software} -async def get_client_db(client_id): +async def get_client_db(client_id: int): query = clients.select().where(clients.c.id == client_id) - return dict(await database.fetch_one(query=query)) + client = await database.fetch_one(query=query) + if client: + return dict(client) + return None async def add_client(data: ClientAndOwnerCreate): client = ClientCreate(**data.dict()) query = clients.insert().values({**client.dict(), "is_active": False, "owner_id": "undefined"}) client_id = await database.execute(query) - owner = OwnerCreate(**data.dict()) - await add_owner(client_id, owner) - return get_client_db(client_id) + owner = OwnerCreate( + email=data.email, + password=data.password, + name=data.owner_name, + surname=data.surname, + client_id=client_id, + is_owner=True, + date_reg=datetime.utcnow() + ) + owner = await add_owner(client_id, owner) + new_client = await get_client_db(client_id) + return new_client async def update_client(id: int, client: ClientUpdate): # TODO проверить работу обновления - query = clients.update().where(clients.c.id == id).values(client.dict()) + client_dict = client.dict() + if "owner_id" in client_dict: + client_dict["owner_id"] = str(client_dict["owner_id"]) + query = clients.update().where(clients.c.id == id).values(**client_dict) client_id = await database.execute(query) - return await get_client_db(client_id) + updated_client = await get_client_db(client_id) + print(updated_client) + return updated_client async def activate_client(id: int): @@ -87,30 +106,29 @@ async def update_owner(client_id: int, new_owner_id: UUID4): if client: client = dict(client) if client["owner_id"] == "undefined": - new_client = ClientUpdate(**client, owner_id=new_owner_id) + # client["owner_id"] = new_owner_id + new_client = ClientCreate(name=client["name"], owner_id=str(new_owner_id)) await update_client(client_id, new_client) - new_owner = get_or_404(new_owner_id) + new_owner = await get_or_404(new_owner_id) else: update_old = UserUpdate(is_owner=False) update_new = UserUpdate(is_owner=True) - new_client = ClientUpdate(**client, owner_id=new_owner_id) + new_client = ClientCreate(**client, owner_id=new_owner_id) old_owner = await update_employee(client["owner_id"], update_old) new_owner = await update_employee(new_owner_id, update_new) client = await update_client(client_id, new_client) return new_owner + return None -async def add_owner(client_id: int, user: UserCreate): - owner = OwnerCreate( - **user.dict(), - client_id=client_id, - is_owner=True, - date_reg=datetime.utcnow()) +async def add_owner(client_id: int, owner: OwnerCreate): + user = UserCreate(**owner.dict()) try: - created_owner = await all_users.create_user(owner, safe=True) + created_owner = await all_users.create_user(user, safe=True) except Exception: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, ) - return await update_owner(client_id, created_owner.id) + updated_owner = await update_owner(client_id, created_owner.id) + return updated_owner diff --git a/src/accounts/developer_account/services.py b/src/accounts/developer_account/services.py index 38d247f..8ab63bc 100644 --- a/src/accounts/developer_account/services.py +++ b/src/accounts/developer_account/services.py @@ -25,6 +25,7 @@ async def add_developer(user: UserCreate): try: created_developer = await all_users.create_user(developer, safe=True) except Exception: + print(Exception) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, diff --git a/src/accounts/employee_account/routers.py b/src/accounts/employee_account/routers.py index 3ce9ffc..781b808 100644 --- a/src/accounts/employee_account/routers.py +++ b/src/accounts/employee_account/routers.py @@ -20,7 +20,7 @@ async def employees_list(id: int, user: UserTable = Depends(any_user)): @employee_router.get("/{id}/employees/{pk}", response_model=UserDB, status_code=status.HTTP_200_OK) -async def employee(id: int, pk: str, user: UserTable = Depends(any_user)): +async def employee(id: int, pk: UUID4, user: UserTable = Depends(any_user)): user = await get_client_users_with_superuser(id, user) return await get_employee(id, pk) diff --git a/src/accounts/employee_account/services.py b/src/accounts/employee_account/services.py index ec2fb46..2fbfd42 100644 --- a/src/accounts/employee_account/services.py +++ b/src/accounts/employee_account/services.py @@ -17,7 +17,7 @@ async def get_employees(id: int): return result -async def get_employee(id: int, pk: str): +async def get_employee(id: int, pk: UUID4): employee = await database.fetch_one(users.select().where((users.c.client_id == id) & (users.c.id == pk))) if employee: employee = dict(employee) diff --git a/src/desk/routes.py b/src/desk/routes.py index 6a07437..55c1443 100644 --- a/src/desk/routes.py +++ b/src/desk/routes.py @@ -34,6 +34,7 @@ async def appeal(id: int, user: UserTable = Depends(employee)): @router.post("/", response_model=AppealDB, status_code=status.HTTP_201_CREATED) async def create_appeal(item: AppealCreate, user: UserTable = Depends(employee)): + # TODO сделать проверку на не разработчика return await add_appeal(item, user) diff --git a/src/desk/services.py b/src/desk/services.py index eab689c..5a10066 100644 --- a/src/desk/services.py +++ b/src/desk/services.py @@ -85,7 +85,7 @@ async def update_appeal(id: int, appeal: AppealUpdate, user: UserTable): old_appeal[field] = appeal[field] query = appeals.update().where(appeals.c.id == id).values(old_appeal) result_id = await database.execute(query) - return await get_appeal_db(result_id) + return await get_appeal_db(id) async def delete_appeal(id: int): diff --git a/src/users/schemas.py b/src/users/schemas.py index b34ea2d..fe89931 100644 --- a/src/users/schemas.py +++ b/src/users/schemas.py @@ -18,10 +18,10 @@ class UserCreate(models.BaseUserCreate): name: str surname: str patronymic: Optional[str] - # is_owner: bool = False - # client_id: int - # date_reg: datetime # TODO попробовать удалить данное поле из регистрации. Пока без нее не работет - # date_block: Optional[datetime] + is_owner: Optional[bool] = False + client_id: Optional[int] + date_reg: datetime = datetime.utcnow() + date_block: Optional[datetime] @validator('password') def valid_password(cls, v: str): @@ -58,8 +58,8 @@ class UserUpdate(User, models.BaseUserUpdate): class UserDB(User, models.BaseUserDB): - is_active: bool + is_active: bool = True is_owner: Optional[bool] client_id: Optional[int] - date_reg: datetime + date_reg: datetime = datetime.utcnow() date_block: Optional[datetime] From c28c5be00ff6c652267dbf2bf70d19f7a128b22e Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Mon, 3 May 2021 22:05:51 +0300 Subject: [PATCH 32/55] add creating a superuser --- src/accounts/developer_account/routers.py | 2 +- src/accounts/developer_account/services.py | 6 ++---- test.db | Bin 118784 -> 118784 bytes 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/accounts/developer_account/routers.py b/src/accounts/developer_account/routers.py index ab53179..a99ef1e 100644 --- a/src/accounts/developer_account/routers.py +++ b/src/accounts/developer_account/routers.py @@ -22,7 +22,7 @@ async def developer(id: UUID4, user: UserTable = Depends(developer_user)): @developer_router.post("/") -async def create_developer(item: UserCreate, user: UserTable = Depends(developer_user)): +async def create_developer(item: UserCreate): return await add_developer(item) diff --git a/src/accounts/developer_account/services.py b/src/accounts/developer_account/services.py index 8ab63bc..97a1aa1 100644 --- a/src/accounts/developer_account/services.py +++ b/src/accounts/developer_account/services.py @@ -19,11 +19,9 @@ async def get_developer(id: UUID4): async def add_developer(user: UserCreate): - developer = DeveloperCreate( - **user.dict(), - date_reg=datetime.utcnow()) + developer = DeveloperCreate(**user.dict()) try: - created_developer = await all_users.create_user(developer, safe=True) + created_developer = await all_users.create_user(developer, safe=False) except Exception: print(Exception) raise HTTPException( diff --git a/test.db b/test.db index ec45bc548b773bff0366745543683a001bb0f75d..ac9a4b57011ce554e507ea67a6cb89e65385b914 100644 GIT binary patch delta 1782 zcmbtVZD?Cn7(VCRb3fDMP17V>+Qy~WTA{YRH))&ng4!~_Hab?fAN#Q%dPkZyCgg@R zMHD1sQwIaJUBpcE$JqW%=os#n6-05vVPPW5L^cE!%4B88><`cy~+HL!X_9N5iG(yyBA>^uES68vz@*#dl(8nt`l#_L8{~0s@CNkLXWfe z#h0IqDrOC|hBnB!N<|bJkk?QxtWm{sUz~(u$n7pGBCTlgW6)j8QsZ4`5KO`_G`eP7 zwiNHU3NhY+wgdtZRL4R;^W?z5Znlw1h>J0Hy{m9!8WG30}3sIhzRbNF=^sgDXhly z?uf2x9Z^;5>5VAOd>JOm_iO$F)=VqGWYV@ds)bv}R;YYXCSzAb@9OD@^v2lLLe{~m zpIy_nUbd_bEvl(KQG16!Waqw`&W&wXzHGxUDST|Ua&9akSSsJYz;~=F{=BSu^%Kwq zj4;2en83@l2#!(5hw)m}D^-8tq1;H+Np0|5S?QP%c3@{$M`C0Q#HLLi0 zh$zzt_&o?-fdSY2KR&+4CkBdd&uz6r?#q}e(`07q>`}b;-<{=?etRE?N)3Xqz;s@4 z9&tXP>{n{6TK9rXcEw7UKE^>DC^a3!TS~#xcx&TpeNmBBBtEu3ZyZPu8QJWRk*@Ua z(zB;=U-2CHyHqV*h?-$f>^2@j+_Q NuZep~7{g1C{{bEyz7YTb delta 1216 zcmb`GUuYav6vpqFduQ(4*_}DNl5w+5mR+ksFecfFt0qCn29-XfwPGIv5z#=GbZL^! zpKXe5p^H>O`;zSlR}pM!B@kJyn~^499}?O}BiKG@5k+f7#e#v>Ke&qCNm}TG76l)^ zJKqf7`EdrmTRFiiC-~f!JIEvKTm3=VIAi4xQ1(_Rvl=n*Up1{^DH#^%NA-o+U4q5*kQexyN@T5 zNqFT^*?$V2l1szj`Hp4EYs2u8zm>WE+h#`ApN7ZehbO^Sv-Z--w?IH}XT;FB^&a6} zDeza>bJmerk0(+oc)4GGwG3`Br4MWVUAkvFbz+9w2Ceob@VA-YY3*J9xZUdam?$$f z7?Rs}!u#qkp;f-~E({qed;RgWKV>(|uTMiWUrnUb@WP;6c?J@)?KF%V-h)PBGvV$o z%ofHxH3d9!YSVrx0j^K6tUTw36=M<@q+f1DiS4@^>$|eil$iDKP7-ghQ zOczFrGv(=#af_RvxujLcj)3vshBx;GL;}5etkj8~6Y}P9mC5oKE`>d;VW{tXkVHU(G`^P4%!RF64zaQ#8^%0KpR=YQ{?e@$~8TlIwE zF@m4r89WxmGe_|7R>}#O1410?&USkp*<43=E|cx;^LqMnUT06Q_i*n+cWWvG1EzUE zzfA}>LP8w8O(?7a#UBa&gbnpjm#r!P1*UZha7c9?;DE*`h=ZHCb%MX+&$u4M^OsSs zcfq}Oi<$`)Q^KWtvhX|Hjqj#|UwH*>&r_(Bkm9p%BK KHe}AvLH;iyoIH*I From 0fd63223f8711881da99721a3e73509c1fc61e7a Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Mon, 24 May 2021 17:29:48 +0500 Subject: [PATCH 33/55] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B0=D0=B2=D1=82=D0=BE=D0=BC=D0=B0=D1=82=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D1=83=D1=8E=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8E=20=D0=BF=D0=B0=D1=80=D0=BE=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/client_account/schemas.py | 4 ++-- src/users/schemas.py | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/accounts/client_account/schemas.py b/src/accounts/client_account/schemas.py index 139fbf2..3f4a892 100644 --- a/src/accounts/client_account/schemas.py +++ b/src/accounts/client_account/schemas.py @@ -5,7 +5,7 @@ from pydantic.types import UUID4 from ...reference_book.schemas import LicenceDB, SoftwareDB -from ...users.schemas import UserDB +from ...users.schemas import UserDB, generate_pwd class ClientBase(BaseModel): @@ -22,7 +22,7 @@ class ClientAndOwnerCreate(ClientCreate): surname: str patronymic: Optional[str] email: EmailStr - password: str + password: str = generate_pwd() is_active: Optional[bool] = True is_superuser: Optional[bool] = False is_verified: Optional[bool] = False diff --git a/src/users/schemas.py b/src/users/schemas.py index fe89931..9feffc5 100644 --- a/src/users/schemas.py +++ b/src/users/schemas.py @@ -1,10 +1,20 @@ from datetime import datetime +import random from fastapi_users import models from pydantic import validator from typing import Optional +def generate_pwd(): + symbols_list = "+-/*!&$#?=@<>abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" + password = "" + length = random.randint(6, 10) + for i in range(length): + password += random.choice(symbols_list) + return password + + class User(models.BaseUser): name: str surname: str @@ -18,6 +28,7 @@ class UserCreate(models.BaseUserCreate): name: str surname: str patronymic: Optional[str] + password: str = generate_pwd() is_owner: Optional[bool] = False client_id: Optional[int] date_reg: datetime = datetime.utcnow() @@ -34,19 +45,19 @@ class EmployeeCreate(UserCreate, models.BaseUserCreate): # avatar is_owner: bool = False client_id: int - date_reg: datetime + date_reg: datetime = datetime.utcnow() class DeveloperCreate(UserCreate, models.BaseUserCreate): # avatar - date_reg: datetime + date_reg: datetime = datetime.utcnow() class OwnerCreate(UserCreate, models.BaseUserCreate): # avatar is_owner: bool = True client_id: int - date_reg: datetime + date_reg: datetime = datetime.utcnow() class UserUpdate(User, models.BaseUserUpdate): From 2c2bf9f2e633a2c816e5ded9e298e452c63eb499 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Mon, 24 May 2021 17:30:11 +0500 Subject: [PATCH 34/55] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=83=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BD=D0=B0=20=D0=BF=D0=BE=D1=87=D1=82=D1=83=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/client_account/services.py | 5 ++++- src/accounts/developer_account/services.py | 4 +++- src/accounts/employee_account/services.py | 12 +++++------ src/service.py | 25 ++++++++++++++++++++++ 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index 540a01a..93bfe80 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -15,7 +15,7 @@ from ...users.logic import all_users, get_or_404 from ...users.models import users from ...users.schemas import UserCreate, OwnerCreate, UserUpdate -from ...service import check_dict +from ...service import check_dict, send_mail async def get_clients(): @@ -130,5 +130,8 @@ async def add_owner(client_id: int, owner: OwnerCreate): status_code=status.HTTP_400_BAD_REQUEST, detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, ) + + message = f"Добро пожаловать в UDV Service Desk!\n\nВаш логин в системе: {owner.email}\nВаш пароль: {owner.password}" + await send_mail(owner.email, "Вы зарегистрированы в системе", message) updated_owner = await update_owner(client_id, created_owner.id) return updated_owner diff --git a/src/accounts/developer_account/services.py b/src/accounts/developer_account/services.py index 97a1aa1..2b4714b 100644 --- a/src/accounts/developer_account/services.py +++ b/src/accounts/developer_account/services.py @@ -5,6 +5,7 @@ from pydantic.types import UUID4 from src.db.db import database +from src.service import send_mail from src.users.logic import all_users, delete_user, update_user from src.users.models import users from src.users.schemas import UserCreate, DeveloperCreate, UserUpdate @@ -28,7 +29,8 @@ async def add_developer(user: UserCreate): status_code=status.HTTP_400_BAD_REQUEST, detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, ) - + message = f"Добро пожаловать в UDV Service Desk!\n\nВаш логин в системе: {user.email}\nВаш пароль: {user.password}" + await send_mail(user.email, "Вы зарегистрированы в системе", message) return created_developer diff --git a/src/accounts/employee_account/services.py b/src/accounts/employee_account/services.py index 2fbfd42..e45fa04 100644 --- a/src/accounts/employee_account/services.py +++ b/src/accounts/employee_account/services.py @@ -6,6 +6,7 @@ from src.db.db import database from src.reference_book.models import licences +from src.service import send_mail from src.users.logic import all_users, update_user, delete_user from src.users.models import users from src.users.schemas import UserCreate, EmployeeCreate, UserUpdate @@ -39,11 +40,9 @@ async def count_allowed_employees(client_id: int): async def add_employee(id: int, user: UserCreate): - employee = EmployeeCreate( - **user.dict(), - client_id=id, - is_owner=False, - date_reg=datetime.utcnow()) + user.client_id = id + user.is_owner = False + employee = EmployeeCreate(**user.dict()) try: created_user = await all_users.create_user(employee, safe=True) except Exception: @@ -51,7 +50,8 @@ async def add_employee(id: int, user: UserCreate): status_code=status.HTTP_400_BAD_REQUEST, detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, ) - + message = f"Добро пожаловать в UDV Service Desk!\n\nВаш логин в системе: {user.email}\nВаш пароль: {user.password}" + await send_mail(user.email, "Вы зарегистрированы в системе", message) return created_user diff --git a/src/service.py b/src/service.py index f98bfba..371ca3c 100644 --- a/src/service.py +++ b/src/service.py @@ -1,6 +1,31 @@ +import asyncio +import smtplib +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart + +from src.config import MAIL_LOGIN, MAIL_PWD async def check_dict(result): if result: return dict(result) return None + + +async def send_mail(to_addr: str, subject: str, msg: str): + multipart_msg = MIMEMultipart() + multipart_msg['From'] = MAIL_LOGIN + multipart_msg['To'] = to_addr + multipart_msg['Subject'] = subject + multipart_msg.attach(MIMEText(msg, 'plain')) + + smtp_obj = smtplib.SMTP('smtp.gmail.com', 587) + smtp_obj.starttls() + smtp_obj.login(MAIL_LOGIN, MAIL_PWD) + smtp_obj.send_message(multipart_msg) + smtp_obj.quit() + print("письмо отправлено") + +if __name__ == '__main__': + print("Hello") + asyncio.run(send_mail("puzanovim@yandex.ru", "HELLO", "Hello Hello")) From cfc6fe60923c2adfaa7b26eeff8ff36b6e829fa3 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sat, 29 May 2021 20:50:16 +0500 Subject: [PATCH 35/55] update user/me --- src/users/routes.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/users/routes.py b/src/users/routes.py index 48f570e..e42abc5 100644 --- a/src/users/routes.py +++ b/src/users/routes.py @@ -1,9 +1,11 @@ from fastapi import Depends, Response from fastapi import APIRouter + +from .schemas import UserDB from ..config import SECRET from src.users.logic import jwt_authentication, all_users, \ - on_after_forgot_password, on_after_reset_password, after_verification, after_verification_request + on_after_forgot_password, on_after_reset_password, after_verification, after_verification_request, any_user router = APIRouter() @@ -13,6 +15,13 @@ async def refresh_jwt(response: Response, user=Depends(all_users.get_current_act return await jwt_authentication.get_login_response(user, response) +@router.get("/me", response_model=UserDB) +async def me( + user: UserDB = Depends(any_user), # type: ignore +): + return user + + router.include_router( all_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", @@ -28,10 +37,11 @@ async def refresh_jwt(response: Response, user=Depends(all_users.get_current_act after_reset_password=on_after_reset_password), prefix="/auth", tags=["auth"]) -router.include_router( - all_users.get_users_router(), - prefix="/users", - tags=["users"]) +# router.include_router( +# all_users.get_users_router(), +# prefix="/users", +# tags=["users"]) + router.include_router( all_users.get_verify_router( SECRET, From a3e7c72c9b14223e820817654400b31817ba42d7 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sat, 29 May 2021 20:53:30 +0500 Subject: [PATCH 36/55] add CORS --- src/app.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/app.py b/src/app.py index fcef522..2303e2d 100644 --- a/src/app.py +++ b/src/app.py @@ -1,16 +1,28 @@ from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware from src.db.db import database, engine from .db.base import Base from .accounts.api import accounts_router from .desk.routes import router as desk_router -from .desk.dev_routes import dev_router from .reference_book.api.routes import router as book_router from .users.routes import router as users_routes Base.metadata.create_all(bind=engine) app = FastAPI() +origins = [ + "http://localhost:3000" +] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + @app.on_event('startup') async def startup(): @@ -30,5 +42,4 @@ def read_root(): app.include_router(accounts_router) app.include_router(book_router, prefix='/references', tags=['Reference book']) app.include_router(desk_router, prefix='/desk', tags=['Desk']) -app.include_router(dev_router, prefix='/desk', tags=['Desk']) app.include_router(users_routes) From ca77a52acdb31a6fd76896dab71575a710a4c967 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sun, 30 May 2021 21:26:47 +0500 Subject: [PATCH 37/55] =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=B2=D0=B5=D0=BB=20ap?= =?UTF-8?q?i=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D1=8B=20=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B2=20=D1=81=D0=BE=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D0=B8=D0=B5=20=D1=81=20=D0=BF=D1=80=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D1=82=D0=B8=D0=BF=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/reference_book/api/licence.py | 38 +--- src/reference_book/api/module.py | 35 ++-- src/reference_book/api/routes.py | 13 +- src/reference_book/api/software.py | 29 +-- src/reference_book/models.py | 34 ++- src/reference_book/schemas.py | 110 ++++++++-- src/reference_book/services.py | 323 +++++++++++++++++++++-------- 7 files changed, 401 insertions(+), 181 deletions(-) diff --git a/src/reference_book/api/licence.py b/src/reference_book/api/licence.py index d767026..1f773ff 100644 --- a/src/reference_book/api/licence.py +++ b/src/reference_book/api/licence.py @@ -1,20 +1,17 @@ from typing import List from fastapi import APIRouter, Depends, status, Response -from ..schemas import Licence, LicenceCreate, LicenceDB -from ..services import get_licence, get_licences, \ - get_client_licences, get_client_licence, add_client_licence, update_client_licence, delete_client_licence, \ - add_licence, update_licence, delete_licence +from ..schemas import Licence, LicenceCreate, LicenceDB, LicenceUpdate, LicencePage +from ..services import get_licence, get_licences, add_licence, update_licence, delete_licence, get_licence_page from ...users.models import UserTable from ...users.logic import developer_user router = APIRouter() -client_licences_router = APIRouter() -@router.get("/", response_model=List[LicenceDB], status_code=status.HTTP_200_OK) +@router.get("/", response_model=LicencePage, status_code=status.HTTP_200_OK) async def licence_list(user: UserTable = Depends(developer_user)): - return await get_licences() + return await get_licence_page() @router.get('/{id}', response_model=Licence, status_code=status.HTTP_200_OK) @@ -28,35 +25,10 @@ async def create_licence(licence: LicenceCreate, user: UserTable = Depends(devel @router.put("/{id}", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) -async def update_licence_by_id(id: int, item: LicenceCreate, user: UserTable = Depends(developer_user)): +async def update_licence_by_id(id: int, item: LicenceUpdate, user: UserTable = Depends(developer_user)): return await update_licence(id, item) @router.delete("/{id}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) async def delete_licence_by_id(id: int, user: UserTable = Depends(developer_user)): await delete_licence(id) - - -@client_licences_router.get("/{id}/licences", response_model=List[LicenceDB], status_code=status.HTTP_200_OK) -async def client_licence_list(id: int, user: UserTable = Depends(developer_user)): - return await get_client_licences(id) - - -@client_licences_router.get('/{id}/licences/{pk}', response_model=Licence, status_code=status.HTTP_200_OK) -async def client_licence(id: int, pk: int, user: UserTable = Depends(developer_user)): - return await get_client_licence(id, pk) - - -@client_licences_router.post("/{id}/licences/", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) -async def create_client_licence(id: int, licence: LicenceCreate, user: UserTable = Depends(developer_user)): - return await add_client_licence(id, licence) - - -@client_licences_router.put("/{id}/licences/{pk}", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) -async def update_client_licence_by_id(id: int, pk: int, item: LicenceCreate, user: UserTable = Depends(developer_user)): - return await update_client_licence(id, pk, item) - - -@client_licences_router.delete("/{id}/licences/{pk}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) -async def delete_client_licence_by_id(id: int, pk: int, user: UserTable = Depends(developer_user)): - await delete_client_licence(id, pk) diff --git a/src/reference_book/api/module.py b/src/reference_book/api/module.py index c059bb5..92fa40b 100644 --- a/src/reference_book/api/module.py +++ b/src/reference_book/api/module.py @@ -1,27 +1,34 @@ +from typing import List + from fastapi import APIRouter, status, Depends, Response -from ..schemas import Module, ModuleCreate, ModuleDB -from ..services import get_module, add_module, delete_module, update_module +from ..schemas import ModuleCreate, ModuleDB, ModuleUpdate +from ..services import get_module, add_module, delete_module, update_module, get_modules from ...users.logic import developer_user from ...users.models import UserTable router = APIRouter() -@router.get('/{id}/modules/{pk}', response_model=Module, status_code=status.HTTP_200_OK) -async def module(id: int, pk: int, user: UserTable = Depends(developer_user)): - return await get_module(id, pk) +@router.get('/', response_model=List[ModuleDB], status_code=status.HTTP_200_OK) +async def modules_list(user: UserTable = Depends(developer_user)): + return await get_modules() + + +@router.get('/{id}', response_model=ModuleDB, status_code=status.HTTP_200_OK) +async def module(id: int, user: UserTable = Depends(developer_user)): + return await get_module(id) -@router.post("/{id}/modules/", response_model=ModuleDB, status_code=status.HTTP_201_CREATED) -async def create_module(id, item: ModuleCreate, user: UserTable = Depends(developer_user)): - return await add_module(id, item) +@router.post("/", response_model=ModuleDB, status_code=status.HTTP_201_CREATED) +async def create_module(item: ModuleCreate, user: UserTable = Depends(developer_user)): + return await add_module(item) -@router.put("/{id}/modules/{pk}", response_model=ModuleDB, status_code=status.HTTP_201_CREATED) -async def update_module_by_id(id: int, pk: int, item: ModuleCreate, user: UserTable = Depends(developer_user)): - return await update_module(id, pk, item) +@router.put("/{id}", response_model=ModuleDB, status_code=status.HTTP_201_CREATED) +async def update_module_by_id(id: int, item: ModuleUpdate, user: UserTable = Depends(developer_user)): + return await update_module(id, item) -@router.delete("/{id}/modules/{pk}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) -async def delete_module_by_id(id: int, pk: int, user: UserTable = Depends(developer_user)): - await delete_module(id, pk) +@router.delete("/{id}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) +async def delete_module_by_id(id: int, user: UserTable = Depends(developer_user)): + await delete_module(id) diff --git a/src/reference_book/api/routes.py b/src/reference_book/api/routes.py index 0c1adb4..1473269 100644 --- a/src/reference_book/api/routes.py +++ b/src/reference_book/api/routes.py @@ -1,27 +1,20 @@ -from typing import List - from fastapi import APIRouter, Depends, status from .software import router as software_router, software_list from .licence import router as licence_router, licence_list -from ..schemas import ModuleShort -from ..services import get_modules +from .module import router as module_router, modules_list from ...users.logic import developer_user from ...users.models import UserTable router = APIRouter() -@router.get("/modules", response_model=List[ModuleShort], status_code=status.HTTP_200_OK) -async def module_list(user: UserTable = Depends(developer_user)): - return await get_modules() - - @router.get('/', status_code=status.HTTP_200_OK) async def get_reference_book(user: UserTable = Depends(developer_user)): licences = await licence_list(user) - modules = await module_list(user) + modules = await modules_list(user) softwares = await software_list(user) return {"licences": licences, "modules": modules, "softwares": softwares} router.include_router(licence_router, prefix='/licences') router.include_router(software_router, prefix='/software') +router.include_router(module_router, prefix='/modules') diff --git a/src/reference_book/api/software.py b/src/reference_book/api/software.py index a107008..ac63ef2 100644 --- a/src/reference_book/api/software.py +++ b/src/reference_book/api/software.py @@ -1,45 +1,34 @@ -from typing import List - from fastapi import APIRouter, status, Depends, Response -from ..services import get_software, get_softwares, get_software_with_modules, \ - add_software, delete_software, update_software -from ..schemas import Software, SoftwareDB, SoftwareCreate +from ..services import get_software_with_modules, delete_software, update_software, \ + get_software_page, add_software_with_modules +from ..schemas import Software, SoftwareDB, SoftwareUpdate, SoftwarePage, SoftwareWithModulesCreate from ...users.logic import developer_user from ...users.models import UserTable -from .module import router as module_router router = APIRouter() -@router.get('/', response_model=List[SoftwareDB], status_code=status.HTTP_200_OK) +@router.get('/', response_model=SoftwarePage, status_code=status.HTTP_200_OK) async def software_list(user: UserTable = Depends(developer_user)): - return await get_softwares() + return await get_software_page() -@router.get('/{id}', response_model=SoftwareDB, status_code=status.HTTP_200_OK) +@router.get('/{id}', response_model=Software, status_code=status.HTTP_200_OK) async def software(id: int, user: UserTable = Depends(developer_user)): - return await get_software(id) - - -@router.get("/{id}/modules", response_model=Software, status_code=status.HTTP_200_OK) -async def get_software_modules(id: int, user: UserTable = Depends(developer_user)): return await get_software_with_modules(id) @router.post("/", response_model=SoftwareDB, status_code=status.HTTP_201_CREATED) -async def create_software(software: SoftwareCreate, user: UserTable = Depends(developer_user)): - return await add_software(software) +async def create_software(new_software: SoftwareWithModulesCreate, user: UserTable = Depends(developer_user)): + return await add_software_with_modules(new_software) @router.put("/{id}", response_model=SoftwareDB, status_code=status.HTTP_201_CREATED) -async def update_software_by_id(id: int, item: SoftwareCreate, user: UserTable = Depends(developer_user)): +async def update_software_by_id(id: int, item: SoftwareUpdate, user: UserTable = Depends(developer_user)): return await update_software(id, item) @router.delete("/{id}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) async def delete_software_by_id(id: int, user: UserTable = Depends(developer_user)): await delete_software(id) - - -router.include_router(module_router) diff --git a/src/reference_book/models.py b/src/reference_book/models.py index 1a8cd59..d8e0dac 100644 --- a/src/reference_book/models.py +++ b/src/reference_book/models.py @@ -10,7 +10,6 @@ class Licence(Base): number = Column(Integer, unique=True, nullable=False) count_members = Column(Integer, default=0, nullable=False) date_end = Column(DateTime(timezone=True), server_default=sql.func.now()) - client_id = Column(Integer, ForeignKey('client.id'), nullable=False) software_id = Column(Integer, ForeignKey('software.id'), nullable=False) @@ -20,7 +19,6 @@ class Module(Base): id = Column(Integer, primary_key=True, index=True, unique=True) name = Column(String, nullable=False) software_id = Column(Integer, ForeignKey('software.id'), nullable=False) # TODO сделать проверку на ключ > 0 - UniqueConstraint(name, software_id) class Software(Base): @@ -30,6 +28,36 @@ class Software(Base): name = Column(String, unique=True, nullable=False) # TODO сделать проверку на непустую строку +class EmployeeLicence(Base): + __tablename__ = 'EmployeeLicence' + + id = Column(Integer, primary_key=True, index=True, unique=True) + employee_id = Column(String, ForeignKey('user.id'), nullable=False, unique=True) + licence_id = Column(Integer, ForeignKey('licence.id'), nullable=False) + UniqueConstraint(employee_id, licence_id) + + +class ClientLicence(Base): + __tablename__ = 'ClientLicence' + + id = Column(Integer, primary_key=True, index=True, unique=True) + client_id = Column(String, ForeignKey('client.id'), nullable=False, unique=True) + licence_id = Column(Integer, ForeignKey('licence.id'), nullable=False) + UniqueConstraint(client_id, licence_id) + + +class SoftwareModules(Base): + __tablename__ = 'SoftwareModules' + + id = Column(Integer, primary_key=True, index=True, unique=True) + software_id = Column(Integer, ForeignKey('software.id'), nullable=False) + module_id = Column(Integer, ForeignKey('module.id'), nullable=False) + UniqueConstraint(software_id, module_id) + + modules = Module.__table__ licences = Licence.__table__ -softwares = Software.__table__ \ No newline at end of file +softwares = Software.__table__ +employee_licences = EmployeeLicence.__table__ +software_modules = SoftwareModules.__table__ +client_licences = ClientLicence.__table__ \ No newline at end of file diff --git a/src/reference_book/schemas.py b/src/reference_book/schemas.py index 3fbf37c..ac79f46 100644 --- a/src/reference_book/schemas.py +++ b/src/reference_book/schemas.py @@ -4,6 +4,22 @@ from pydantic import BaseModel +class ModuleBase(BaseModel): + name: str + + +class ModuleCreate(ModuleBase): + pass + + +class ModuleUpdate(ModuleBase): + pass + + +class ModuleDB(ModuleBase): + id: int + + class SoftwareBase(BaseModel): name: str = '' @@ -12,64 +28,118 @@ class SoftwareCreate(SoftwareBase): pass +class SoftwareWithModulesCreate(SoftwareCreate): + modules: List[int] + + +class SoftwareUpdate(SoftwareBase): + pass + + class SoftwareDB(SoftwareBase): id: int - class Config: - orm_mode = True + +class Software(SoftwareBase): + id: int + modules: List[ModuleDB] = None + + +class SoftwarePage(BaseModel): + software_list: List[Software] + module_list: List[ModuleDB] class LicenceBase(BaseModel): - number: int count_members: int date_end: datetime class LicenceCreate(LicenceBase): - client_id: int + number: int software_id: int -# class LicenceShort(LicenceBase): -# id: int -# client_id: int -# software_id: int # TODO заменить на str +class LicenceUpdate(BaseModel): + pass class LicenceDB(LicenceBase): id: int - client_id: int + number: int software_id: int class Licence(LicenceBase): id: int - client_id: int + number: int + closed_vacancies: int = -1 # TODO значение -1 для теста software: SoftwareDB -class ModuleBase(BaseModel): - name: str +class LicencePage(BaseModel): + licences_list: List[Licence] + software_list: List[SoftwareDB] -class ModuleCreate(ModuleBase): +class EmployeeLicenceBase(BaseModel): pass -class ModuleDB(ModuleBase): +class EmployeeLicenceCreate(EmployeeLicenceBase): + employee_id: str + licence_id: int + + +class EmployeeLicenceUpdate(EmployeeLicenceBase): + licence_id: int + + +class EmployeeLicenceDB(EmployeeLicenceBase): id: int - software_id: int + employee_id: str + licence_id: int -class Module(ModuleBase): +class EmployeeLicence(EmployeeLicenceBase): id: int - software: SoftwareDB + employee_id: str + licence: LicenceDB + +class ClientLicenceBase(BaseModel): + pass + + +class ClientLicenceCreate(EmployeeLicenceBase): + client_id: int + licence_id: int -class ModuleShort(ModuleBase): + +class ClientLicenceUpdate(EmployeeLicenceBase): + licence_id: int + + +class ClientLicenceDB(EmployeeLicenceBase): id: int + client_id: int + licence_id: int -class Software(SoftwareBase): +class ClientLicence(EmployeeLicenceBase): + id: int + client_id: int + licence: LicenceDB + + +class SoftwareModulesBase(BaseModel): + software_id: int + module_id: int + + +class SoftwareModulesCreate(SoftwareModulesBase): + pass + + +class SoftwareModulesDB(SoftwareModulesBase): id: int - modules: List[ModuleShort] = None diff --git a/src/reference_book/services.py b/src/reference_book/services.py index 2777b5c..8e8bffa 100644 --- a/src/reference_book/services.py +++ b/src/reference_book/services.py @@ -1,150 +1,311 @@ -from .schemas import ModuleCreate, LicenceCreate, SoftwareCreate +from pydantic.types import UUID4 + +from .schemas import ModuleCreate, LicenceCreate, SoftwareCreate, EmployeeLicenceCreate, EmployeeLicenceUpdate, \ + SoftwareUpdate, SoftwareDB, Software, ModuleDB, ModuleUpdate, LicenceDB, Licence, LicenceUpdate, \ + EmployeeLicenceDB, SoftwareModulesCreate, SoftwareModulesDB, SoftwarePage, \ + SoftwareWithModulesCreate, LicencePage, ClientLicenceDB, ClientLicenceCreate from ..db.db import database -from .models import softwares, modules, licences -from ..accounts.client_account.services import activate_client +from .models import softwares, modules, licences, employee_licences, software_modules, client_licences +from ..errors import Errors +from typing import List, Optional + + +async def get_software_list() -> List[Software]: + result = await database.fetch_all(query=softwares.select()) + list_of_software = [] + for software in result: + software = dict(software) + module = await get_software_modules(software["id"]) + list_of_software.append(Software(**dict({**software, "modules": module}))) + return list_of_software -async def get_softwares(): +async def get_software_db_list() -> List[SoftwareDB]: result = await database.fetch_all(query=softwares.select()) - return [dict(software) for software in result] + return [SoftwareDB(**dict(software)) for software in result] + + +async def get_software_page() -> SoftwarePage: + software_list = await get_software_list() + modules_list = await get_modules() + return SoftwarePage(**dict({"software_list": software_list, "modules_list": modules_list})) -async def get_software(id: int): - result = await database.fetch_one(query=softwares.select().where(softwares.c.id == id)) +async def get_software(software_id: int) -> Optional[SoftwareDB]: + result = await database.fetch_one(query=softwares.select().where(softwares.c.id == software_id)) if result is not None: - return dict(result) + return SoftwareDB(**dict(result)) return None -async def get_software_with_modules(id: int): - result = await database.fetch_one(query=softwares.select().where(softwares.c.id == id)) +async def get_software_by_name(software_name: str) -> Optional[SoftwareDB]: + result = await database.fetch_one(query=softwares.select().where(softwares.c.name == software_name)) if result is not None: - result = dict(result) - module = await get_software_modules(id) - return {**result, "modules": module} + return SoftwareDB(**dict(result)) return None -async def add_software(software: SoftwareCreate): +async def get_software_with_modules(software_id: int) -> Optional[Software]: + result = await database.fetch_one(query=softwares.select().where(softwares.c.id == software_id)) + if result is not None: + module = await get_software_modules(software_id) + return Software(**dict({**dict(result), "modules": module})) + return None + + +async def add_software(software: SoftwareCreate) -> SoftwareDB: + if get_software_by_name(software.name) is not None: + raise Errors.SOFTWARE_IS_EXIST query = softwares.insert().values(**software.dict()) - id = await database.execute(query) - return {"id": id, **software.dict()} + software_id = await database.execute(query) + return SoftwareDB(**dict({"id": software_id, **software.dict()})) + + +async def add_software_with_modules(software_with_modules: SoftwareWithModulesCreate) -> SoftwareDB: + software = await add_software(SoftwareCreate(name=software_with_modules.name)) + modules_list = software_with_modules.modules + for module_id in modules_list: + try: # TODO безопасное добавление + module = await add_software_module(software.id, module_id) + except Exception as e: + print(e) + return software -async def update_software(id: int, software: SoftwareCreate): - query = softwares.update().where(softwares.c.id == id).values(**software.dict()) +async def update_software(software_id: int, software: SoftwareUpdate) -> SoftwareDB: + if get_software(software_id) is None: + raise Errors.SOFTWARE_IS_NOT_EXIST + query = softwares.update().where(softwares.c.id == software_id).values(**software.dict()) await database.execute(query) - return {"id": id, **software.dict()} + return SoftwareDB(**dict({"id": software_id, **software.dict()})) -async def delete_software(id: int): - query = softwares.delete().where(softwares.c.id == id) +async def delete_software(software_id: int) -> None: # TODO safe delete + if get_software(software_id) is None: + raise Errors.SOFTWARE_IS_NOT_EXIST + query = softwares.delete().where(softwares.c.id == software_id) await database.execute(query) -async def get_modules(): - result = await database.fetch_all(query=modules.select()) - return [dict(module) for module in result] +async def get_software_module(software_id: int, module_id: int) -> Optional[SoftwareModulesDB]: + query = software_modules.select(). \ + where((software_modules.c.software_id == software_id) & (software_modules.c.module_id == module_id)) + result = await database.fetch_one(query=query) + if result: + return SoftwareModulesDB(**dict(result)) + return None -async def get_software_modules(id: int): - result = await database.fetch_all(query=modules.select().where(modules.c.software_id == id)) - return [dict(module) for module in result] +async def get_software_modules(software_id: int) -> List[ModuleDB]: + query = software_modules.select().where(software_modules.c.software_id == software_id) + result = await database.fetch_all(query=query) + return [await get_module(software_module.module_id) for software_module in + result] # TODO проверить работу метода (software_module.module_id) -async def get_module(id: int, pk: int): - query = modules.select().where((modules.c.software_id == id) & (modules.c.id == pk)) - result = await database.fetch_one(query=query) +async def add_software_module(software_id: int, module_id: int) -> SoftwareModulesDB: + if await get_software_module(software_id, module_id) is not None: + raise Errors.MODULE_FOR_THIS_SOFTWARE_IS_EXIST + software_module = SoftwareModulesCreate(**dict({"software_id": software_id, "module_id": module_id})) + query = software_modules.insert().values(software_module.dict()) + software_module_id = await database.execute(query) + return SoftwareModulesDB(**dict({"id": software_module_id, **software_module.dict()})) + + +async def delete_software_module(software_id: int, module_id: int) -> None: + if await get_software_module(software_id, module_id) is None: + raise Errors.MODULE_FOR_THIS_SOFTWARE_IS_NOT_EXIST + query = software_modules.delete(). \ + where((software_modules.c.software_id == software_id) & (software_modules.c.module_id == module_id)) + await database.execute(query) + + +async def get_modules() -> List[ModuleDB]: + result = await database.fetch_all(query=modules.select()) + return [ModuleDB(**dict(module)) for module in result] + + +async def get_module(module_id: int) -> Optional[ModuleDB]: + result = await database.fetch_one(query=modules.select().where(modules.c.id == module_id)) if result is not None: - module = dict(result) - software = await get_software(id) - return {**module, "software": software} + return ModuleDB(**dict(result)) return None -async def get_module_db(id: int): - return dict(await database.fetch_one(modules.select().where(modules.c.id == id))) +async def get_module_by_name(module_name: str) -> Optional[ModuleDB]: + result = await database.fetch_one(query=modules.select().where(modules.c.name == module_name)) + if result is not None: + return ModuleDB(**dict(result)) + return None -async def add_module(id: int, module: ModuleCreate): - query = modules.insert().values({**module.dict(), "software_id": id}) +async def add_module(module: ModuleCreate) -> ModuleDB: + if await get_module_by_name(module.name) is not None: + raise Errors.MODULE_IS_EXIST + query = modules.insert().values(**module.dict()) module_id = await database.execute(query) - return {"id": module_id, **module.dict(), "software_id": id} + return ModuleDB(**dict({"id": module_id, **module.dict()})) -async def update_module(id: int, pk: int, module: ModuleCreate): - query = modules.update().where((modules.c.software_id == id) & (modules.c.id == pk)).values(**module.dict()) +async def update_module(module_id: int, module: ModuleUpdate) -> ModuleDB: + if await get_module(module_id) is None: + raise Errors.MODULE_IS_NOT_EXIST + query = modules.update().where(modules.c.id == module_id).values(**module.dict()) await database.execute(query) - return {"id": pk, **module.dict(), "software_id": id} + return ModuleDB(**dict({"id": module_id, **module.dict()})) -async def delete_module(id: int, pk: int): - query = modules.delete().where((modules.c.software_id == id) & (modules.c.id == pk)) +async def delete_module(module_id: int) -> None: + if await get_module(module_id) is None: + raise Errors.MODULE_IS_NOT_EXIST + query = modules.delete().where(modules.c.id == module_id) await database.execute(query) -async def get_licences(): +async def get_licences() -> List[Licence]: result = await database.fetch_all(query=licences.select()) - return [dict(licence) for licence in result] + licences_list = [] + for licence in result: + licence = dict(licence) + software = await get_software(licence["software_id"]) + closed_vacancies = await get_count_employee_for_licence_id(licence["id"]) + licences_list.append(Licence(**dict({**licence, "software": software, "closed_vacancies": closed_vacancies}))) + return licences_list + + +async def get_licence_page() -> LicencePage: + licences_list = await get_licences() + software_list = await get_software_db_list() + return LicencePage(**dict({"licences_list": licences_list, "software_list": software_list})) -async def get_licence(id: int): - result = await database.fetch_one(query=licences.select().where(licences.c.id == id)) +async def get_licence(licence_id: int) -> Optional[Licence]: + result = await database.fetch_one(query=licences.select().where(licences.c.id == licence_id)) if result is not None: licence = dict(result) software = await get_software(licence["software_id"]) - return {**licence, "software": software} + closed_vacancies = await get_count_employee_for_licence_id(licence["id"]) + return Licence(**dict({**licence, "software": software, "closed_vacancies": closed_vacancies})) return None -async def get_client_licences(id: int): - result = await database.fetch_all(query=licences.select().where(licences.c.client_id == id)) - return [dict(licence) for licence in result] +async def get_licence_db(licence_id: int) -> Optional[LicenceDB]: + result = await database.fetch_one(licences.select().where(licences.c.id == licence_id)) + if result: + return LicenceDB(**dict(result)) + return None # TODO может быть стоит бросать ошибку если не нашли запись -async def get_client_licence(id: int, pk: int): - query = licences.select().where((licences.c.client_id == id) & (licences.c.id == pk)) - result = await database.fetch_one(query=query) - if result is not None: - licence = dict(result) - software = await get_software(licence["software_id"]) - return {**licence, "software": software} +async def get_licence_by_number(licence_number: int) -> Optional[LicenceDB]: + result = await database.fetch_one(licences.select().where(licences.c.id == licence_number)) + if result: + return LicenceDB(**dict(result)) return None -async def add_licence(licence: LicenceCreate): - item = {**licence.dict()} - query = licences.insert().values(item) +async def add_licence(licence: LicenceCreate) -> LicenceDB: + if await get_licence_by_number(licence.number) is not None: + raise Errors.LICENCE_IS_EXIST + query = licences.insert().values(licence.dict()) licence_id = await database.execute(query) - await activate_client(item["client_id"]) - return {"id": licence_id, **licence.dict()} + # await activate_client(item["client_id"]) # TODO активация клиента + return LicenceDB(**dict({"id": licence_id, **licence.dict()})) -async def update_licence(pk: int, licence: LicenceCreate): - query = licences.update().where(licences.c.id == pk).values(**licence.dict()) +async def update_licence(licence_id: int, licence: LicenceUpdate) -> LicenceDB: + if await get_licence_db(licence_id) is None: + raise Errors.LICENCE_IS_NOT_EXIST + query = licences.update().where(licences.c.id == licence_id).values(**licence.dict()) await database.execute(query) - return {"id": pk, **licence.dict()} + return await get_licence_db(licence_id) -async def delete_licence(pk: int): - query = licences.delete().where(licences.c.id == pk) +async def delete_licence(licence_id: int) -> None: + if await get_licence_db(licence_id) is None: + raise Errors.LICENCE_IS_NOT_EXIST + query = licences.delete().where(licences.c.id == licence_id) await database.execute(query) -async def add_client_licence(id: int, licence: LicenceCreate): - item = {**licence.dict(), "client_id": id} - query = licences.insert().values(item) - licence_id = await database.execute(query) - await activate_client(id) - return {"id": licence_id, **licence.dict(), "client_id": id} +async def get_count_employee_for_licence_id(licence_id: int) -> int: + query = employee_licences.select().where(employee_licences.c.licence_id == licence_id) + result = await database.fetch_all(query=query) + if result is not None: + print(result) # TODO посмотреть можно ли просто вывести len() без преобразования + return len([dict(licence) for licence in result]) + return 0 -async def update_client_licence(id: int, pk: int, licence: LicenceCreate): - query = licences.update().where((licences.c.client_id == id) & (licences.c.id == pk)).values(**licence.dict()) - await database.execute(query) - return {"id": pk, **licence.dict(), "client_id": id} +async def get_free_vacancy_in_licence(licence_id: int) -> int: + licence = await get_licence_db(licence_id) + count_employees = await get_count_employee_for_licence_id(licence_id) + if licence is not None: + return licence.count_members - count_employees + return 0 -async def delete_client_licence(id: int, pk: int): - query = licences.delete().where((licences.c.client_id == id) & (licences.c.id == pk)) - await database.execute(query) +async def get_employee_licence(employee_id: UUID4) -> Optional[LicenceDB]: + query = employee_licences.select().where(employee_licences.c.employee_id == employee_id) + result = await database.fetch_one(query=query) + if result: + employee_licence = dict(result) + licence = await get_licence_db(employee_licence["licence_id"]) + return licence + return None + + +async def add_employee_licence(employee_id: UUID4, licence_id: int) -> EmployeeLicenceDB: + if await get_employee_licence(employee_id) is not None: + raise Errors.USER_HAS_ANOTHER_LICENCE + if await get_licence_db(licence_id) is None: + raise Errors.LICENCE_IS_NOT_EXIST + if await get_free_vacancy_in_licence(licence_id) <= 0: + raise Errors.LICENCE_IS_FULL + employee_licence = EmployeeLicenceCreate(**dict({"employee_id": employee_id, "licence_id": licence_id})) + query = employee_licences.insert().values(**employee_licence.dict()) + employee_licence_id = await database.execute(query) + return EmployeeLicenceDB(**dict({"id": employee_licence_id, **employee_licence.dict()})) + + +async def update_employee_licence(employee_id: UUID4, licence_id: int) -> EmployeeLicenceDB: + if await get_free_vacancy_in_licence(licence_id) <= 0: + raise Errors.LICENCE_IS_FULL + employee_licence = EmployeeLicenceUpdate(**dict({"licence_id": licence_id})) + query = employee_licences.update().where(employee_licences.c.employee_id == employee_id).values( + **employee_licence.dict()) + employee_licence_id = await database.execute(query) + return EmployeeLicenceDB(**dict({"id": employee_licence_id, "employee_id": employee_id, **employee_licence.dict()})) + + +async def get_client_licence(client_id: int, licence_id: int) -> Optional[ClientLicenceDB]: # TODO чекнуть использование данных методов + query = client_licences.select().\ + where((client_licences.c.client_id == client_id) & (client_licences.c.licence_id == licence_id)) + result = await database.fetch_one(query=query) + if result: + return ClientLicenceDB(**dict(result)) + return None + + +async def get_client_licences(client_id: int) -> List[Licence]: + query = client_licences.select().where(client_licences.c.client_id == client_id) + result = await database.fetch_all(query=query) + client_licences_list = [] + for client_licence in result: + client_licence = dict(client_licence) + licence = dict(await get_licence_db(client_licence["licence_id"])) + closed_vacancies = await get_count_employee_for_licence_id(client_licence["licence_id"]) + software = await get_software(licence["software_id"]) # TODO разобраться с None + client_licences_list.append( + Licence(**dict({**licence, "closed_vacancies": closed_vacancies, "software": software}))) + return client_licences_list + + +async def add_client_licence(client_id: int, licence_id: int) -> ClientLicenceDB: + if await get_client_licence(client_id, licence_id) is not None: + raise Errors.CLIENT_HAS_THIS_LICENCE + if await get_licence_db(licence_id) is None: + raise Errors.LICENCE_IS_NOT_EXIST + client_licence = ClientLicenceCreate(**dict({"client_id": client_id, "licence_id": licence_id})) + query = client_licences.insert().values(**client_licence.dict()) + employee_licence_id = await database.execute(query) + return ClientLicenceDB(**dict({"id": employee_licence_id, **client_licence.dict()})) From 74ac0e5c0162886262de199233bcbb73c68b7c5c Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sun, 30 May 2021 21:27:27 +0500 Subject: [PATCH 38/55] =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=B2=D0=B5=D0=BB=20ap?= =?UTF-8?q?i=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D1=8B=20=D0=BE=D0=B1?= =?UTF-8?q?=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B2=20=D1=81?= =?UTF-8?q?=D0=BE=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82=D0=B2=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=81=20=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D1=82=D0=B8?= =?UTF-8?q?=D0=BF=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/desk/dev_routes.py | 14 ----- src/desk/models.py | 2 + src/desk/routes.py | 40 +++++-------- src/desk/schemas.py | 101 +++++++++++++++++++++----------- src/desk/services.py | 128 ++++++++++++++++++++++++++++------------- 5 files changed, 169 insertions(+), 116 deletions(-) delete mode 100644 src/desk/dev_routes.py diff --git a/src/desk/dev_routes.py b/src/desk/dev_routes.py deleted file mode 100644 index 1fd9b4c..0000000 --- a/src/desk/dev_routes.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import APIRouter, status, Depends -from typing import List - -from src.desk.schemas import AppealBase -from src.desk.services import get_all_appeals -from src.users.logic import developer_user -from src.users.models import UserTable - -dev_router = APIRouter() - - -# @dev_router.get("/", response_model=List[AppealBase], status_code=status.HTTP_200_OK) -# async def all_appeals_list(user: UserTable = Depends(developer_user)): -# return await get_all_appeals() diff --git a/src/desk/models.py b/src/desk/models.py index b5460f5..7d9d0e3 100644 --- a/src/desk/models.py +++ b/src/desk/models.py @@ -25,9 +25,11 @@ class Appeal(Base): responsible_id = Column(String, ForeignKey('user.id'), nullable=True) status = Column(Enum(StatusTasks), default=StatusTasks.new) date_create = Column(DateTime(timezone=True), server_default=sql.func.now()) + date_edit = Column(DateTime, default=None) date_processing = Column(DateTime, default=None) software_id = Column(Integer, ForeignKey('software.id'), nullable=False) module_id = Column(Integer, ForeignKey('module.id'), nullable=False) + importance = Column(Integer, default=1) # attachments diff --git a/src/desk/routes.py b/src/desk/routes.py index 55c1443..c45d11c 100644 --- a/src/desk/routes.py +++ b/src/desk/routes.py @@ -1,10 +1,8 @@ -from fastapi import APIRouter, status, Depends, Response -from typing import List +from fastapi import APIRouter, status, Depends, Response, HTTPException +from typing import List, Union -from pydantic.types import UUID4 - -from .services import get_all_appeals, get_appeals, get_appeal, get_comments, get_comment, \ - add_appeal, add_comment, update_appeal, update_comment, delete_comment, update_attachments +from .services import get_all_appeals, get_appeal, get_comments, get_comment, \ + add_appeal, add_comment, update_appeal, update_comment, delete_comment, get_appeals_page from .schemas import Appeal, CommentShort, Comment, AppealCreate, CommentCreate, CommentDB, \ AppealUpdate, AppealDB, AppealShort, CommentUpdate, DevAppeal from ..users.models import UserTable @@ -17,36 +15,24 @@ async def appeals_list(user: UserTable = Depends(any_user)): if user.is_superuser: return await get_all_appeals() - return await get_appeals(user) - - -@router.get("/{id}", status_code=status.HTTP_200_OK) -async def appeal(id: int, user: UserTable = Depends(employee)): - return await get_appeal(id, user) + return await get_appeals_page(user) -# @router.get("/{id}", response_model=DevAppeal, status_code=status.HTTP_200_OK) -# async def appeal(id: int, user: UserTable = Depends(developer_user)): -# print("WINDOW OF DEVELOPER") -# print(user.is_superuser) -# return await get_appeal(id, user) +@router.get("/{id}", response_model=Union[Appeal, DevAppeal], status_code=status.HTTP_200_OK) +async def appeal(appeal_id: int, user: UserTable = Depends(any_user)): + return await get_appeal(appeal_id, user) @router.post("/", response_model=AppealDB, status_code=status.HTTP_201_CREATED) async def create_appeal(item: AppealCreate, user: UserTable = Depends(employee)): - # TODO сделать проверку на не разработчика + if user.is_superuser is True: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) return await add_appeal(item, user) -# @router.patch("/{id}", status_code=status.HTTP_201_CREATED) -# async def update_attachments_on_appeal(id: int, item: AppealUpdate, user: UserTable = Depends(employee)): -# pass - # return await update_attachments(id, item, user) - - -@router.patch("/{id}", response_model=AppealDB, status_code=status.HTTP_201_CREATED) -async def update_appeal_by_id(id: int, item: AppealUpdate, user: UserTable = Depends(developer_user)): - return await update_appeal(id, item, user) +@router.patch("/{id}", response_model=AppealDB, status_code=status.HTTP_201_CREATED) # TODO сделать изменение обращения пользователем +async def update_appeal_by_id(appeal_id: int, item: AppealUpdate, user: UserTable = Depends(developer_user)): + return await update_appeal(appeal_id, item, user) @router.get("/{id}/comments", response_model=List[CommentShort], status_code=status.HTTP_200_OK) diff --git a/src/desk/schemas.py b/src/desk/schemas.py index 682b51e..6ea1fd9 100644 --- a/src/desk/schemas.py +++ b/src/desk/schemas.py @@ -4,19 +4,55 @@ from .models import StatusTasks from pydantic import BaseModel -from ..accounts.client_account.schemas import ClientDB -from ..reference_book.schemas import SoftwareDB, ModuleShort +from ..accounts.client_account.schemas import ClientDB, Client +from ..reference_book.schemas import SoftwareDB, ModuleDB from ..users.schemas import UserDB +class CommentBase(BaseModel): + text: str + + +class CommentCreate(CommentBase): + pass + + +class CommentUpdate(CommentCreate): + pass + + +class Comment(CommentBase): + id: int + appeal_id: int + author: UserDB + date_create: datetime + + +class CommentDB(CommentBase): + id: int + appeal_id: int + author_id: str + date_create: datetime + + +class CommentShort(CommentBase): + id: int + author_id: str + date_create: datetime + + class AppealBase(BaseModel): topic: str - text: str + importance: int + date_edit: Optional[datetime] class AppealCreate(AppealBase): + text: str software_id: int module_id: int + date_create: datetime = datetime.utcnow() + importance: Optional[int] = 1 class AppealUpdate(AppealBase): @@ -26,15 +62,19 @@ class AppealUpdate(AppealBase): software_id: Optional[int] module_id: Optional[int] responsible_id: Optional[str] + importance: Optional[int] + date_edit: datetime = datetime.utcnow() class AppealShort(AppealBase): id: int + text: str status: StatusTasks class AppealDB(AppealBase): id: int + text: str client_id: int author_id: str status: StatusTasks @@ -45,51 +85,42 @@ class AppealDB(AppealBase): module_id: int -class CommentBase(BaseModel): - text: str - - -class CommentCreate(CommentBase): - pass - - -class CommentUpdate(CommentCreate): - pass - - -class Comment(CommentBase): +class AppealList(AppealBase): id: int - appeal_id: int + importance: int + title: str author: UserDB + client: ClientDB date_create: datetime - - -class CommentDB(CommentBase): - id: int - appeal_id: int - author_id: str - date_create: datetime - - -class CommentShort(CommentBase): - id: int - author_id: str - date_create: datetime + responsible: UserDB + status: StatusTasks + software: SoftwareDB + module: ModuleDB class Appeal(AppealBase): id: int - client: ClientDB - author: UserDB - responsible: Optional[UserDB] + text: str status: StatusTasks date_create: datetime date_processing: Optional[datetime] + client: ClientDB + author: UserDB + responsible: Optional[UserDB] software: SoftwareDB - module: ModuleShort - comment: Optional[List[CommentShort]] + module: ModuleDB + comments: Optional[List[CommentShort]] + + +class AppealsPage(BaseModel): + appeals: List[AppealList] + client: Client + software_list: List[SoftwareDB] + modules_list: List[ModuleDB] class DevAppeal(Appeal): + software_list: List[SoftwareDB] + modules_list: List[ModuleDB] developers: List[UserDB] allowed_statuses: List[StatusTasks] diff --git a/src/desk/services.py b/src/desk/services.py index 5a10066..c695101 100644 --- a/src/desk/services.py +++ b/src/desk/services.py @@ -1,12 +1,18 @@ from datetime import datetime +from typing import List, Union -from .schemas import AppealCreate, CommentCreate, AppealUpdate, CommentUpdate +from pydantic.types import UUID4 + +from .schemas import AppealCreate, CommentCreate, AppealUpdate, CommentUpdate, AppealList, AppealDB, Appeal, DevAppeal, \ + AppealsPage from ..accounts.client_account.models import clients +from ..accounts.client_account.services import get_client_db, get_client from ..db.db import database from .models import appeals, comments from ..errors import Errors from ..reference_book.models import softwares, modules -from ..users.logic import get_developers +from ..reference_book.services import get_software, get_module, get_modules, get_software_list, get_software_db_list +from ..users.logic import get_developers, get_or_404, get_user from ..users.models import UserTable, users from .models import StatusTasks from ..service import check_dict @@ -14,53 +20,89 @@ from fastapi import HTTPException, status -async def get_all_appeals(): +async def get_all_appeals() -> List[AppealList]: result = await database.fetch_all(query=appeals.select()) - return [dict(appeal) for appeal in result] - - -async def get_appeals(user: UserTable): + appeals_list = [] + for appeal in result: + appeal = dict(appeal) + author = await get_or_404(appeal["author_id"]) + client = await get_client_db(appeal["client_id"]) + responsible = await get_or_404(appeal["responsible_id"]) + software = await get_software(appeal["software_id"]) + module = await get_module(appeal["module_id"]) + appeals_list.append(AppealList(**dict({ + **appeal, + "author": author, + "client": client, + "responsible": responsible, + "software": software, + "module": module}))) + return appeals_list + + +async def get_appeals(user: UserTable) -> List[Appeal]: query = appeals.select().where(appeals.c.client_id == user.client_id) result = await database.fetch_all(query=query) - return [dict(appeal) for appeal in result] - - -async def get_appeal(id: int, user: UserTable): - appeal = await check_access(id, user, status.HTTP_404_NOT_FOUND) - client = await check_dict(await database.fetch_one(query=clients.select().where(clients.c.id == appeal["client_id"]))) - author = await check_dict(await database.fetch_one(query=users.select().where(users.c.id == appeal["author_id"]))) - responsible = await check_dict( - await database.fetch_one(query=users.select().where(users.c.id == appeal["responsible_id"]))) - software = await check_dict( - await database.fetch_one(query=softwares.select().where(softwares.c.id == appeal["software_id"]))) - module = await check_dict(await database.fetch_one(query=modules.select().where(modules.c.id == appeal["module_id"]))) - comment = await get_comments(id, user) - result = {**appeal, - "client": client, - "author": author, - "responsible": responsible, - "software": software, - "module": module, - "comment": comment} + appeals_list = [] + for appeal in result: + appeal = dict(appeal) + correct_appeal = await get_appeal(appeal["id"], user) + appeals_list.append(Appeal(correct_appeal)) + return appeals_list + + +async def get_appeals_page(user: UserTable) -> AppealsPage: + appeals_list = await get_appeals(user) + client = await get_client(user.client_id) + software_list = await get_software_db_list() + modules_list = await get_modules() + return AppealsPage(**dict({"appeals": appeals_list, + "client": client, + "software_list": software_list, + "modules_list": modules_list})) + + +async def get_appeal(appeal_id: int, user: UserTable) -> Union[Appeal, DevAppeal]: + appeal = await check_access(appeal_id, user, status.HTTP_404_NOT_FOUND) + # TODO проверить на несуществующее поле или на None + client = await get_client_db(appeal.client_id) + author = await get_user(UUID4(appeal.author_id)) + responsible = None + if appeal.responsible_id: + responsible = await get_user(UUID4(appeal.responsible_id)) + software = await get_software(appeal.software_id) + module = await get_module(appeal.module_id) + comment = await get_comments(appeal_id, user) + result = Appeal(**dict({**appeal, + "client": client, + "author": author, + "responsible": responsible, + "software": software, + "module": module, + "comments": comment})) if user.is_superuser: developers = await get_developers() - allowed_statuses = await get_next_status(id, user) - return {**result, - "developers": developers, - "allowed_statuses": allowed_statuses} + allowed_statuses = await get_next_status(appeal_id, user) + modules_list = await get_modules() + software_list = await get_software_db_list() + result = DevAppeal(**dict({**result, + "software_list": software_list, + "modules_list": modules_list, + "developers": developers, + "allowed_statuses": allowed_statuses})) return result -async def get_appeal_db(id: int): +async def get_appeal_db(id: int) -> AppealDB: query = appeals.select().where(appeals.c.id == id) result = await database.fetch_one(query=query) if result: - return dict(result) + return AppealDB(**dict(result)) else: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) -async def add_appeal(appeal: AppealCreate, user: UserTable): +async def add_appeal(appeal: AppealCreate, user: UserTable) -> AppealDB: item = {**appeal.dict(), "client_id": int(user.client_id), "author_id": str(user.id), "status": StatusTasks.new} query = appeals.insert().values(item) appeal_id = await database.execute(query) @@ -72,20 +114,26 @@ async def update_attachments(id: int, appeal: AppealUpdate, user: UserTable): pass -async def update_appeal(id: int, appeal: AppealUpdate, user: UserTable): - old_appeal = await get_appeal_db(id) - if old_appeal["status"] == StatusTasks.closed or old_appeal["status"] == StatusTasks.canceled: +async def update_appeal(appeal_id: int, appeal: AppealUpdate, user: UserTable) -> AppealDB: + old_appeal = await get_appeal_db(appeal_id) + if old_appeal.status == StatusTasks.closed or old_appeal.status == StatusTasks.canceled: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=Errors.APPEAL_IS_CLOSED) appeal = appeal.dict(exclude_unset=True) if "status" in appeal and (appeal["status"] == StatusTasks.closed or appeal["status"] == StatusTasks.canceled): appeal["date_processing"] = datetime.utcnow() - if "responsible_id" in appeal and old_appeal["status"] == StatusTasks.new: + if "responsible_id" in appeal and old_appeal.status == StatusTasks.new: appeal["status"] = StatusTasks.registered + if "importance" in appeal: + if appeal["importance"] < 0: + appeal["importance"] = 1 + if appeal["importance"] > 5: + appeal["importance"] = 5 + old_appeal = dict(old_appeal) for field in appeal: old_appeal[field] = appeal[field] - query = appeals.update().where(appeals.c.id == id).values(old_appeal) + query = appeals.update().where(appeals.c.id == appeal_id).values(old_appeal) result_id = await database.execute(query) - return await get_appeal_db(id) + return await get_appeal_db(appeal_id) async def delete_appeal(id: int): From 3585892bae5c4e20b4be8786b1d8ff8f78fde2e9 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sun, 30 May 2021 21:27:57 +0500 Subject: [PATCH 39/55] =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=B2=D0=B5=D0=BB=20ap?= =?UTF-8?q?i=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D1=8B=20=D0=BB=D0=B8?= =?UTF-8?q?=D1=87=D0=BD=D1=8B=D1=85=20=D0=BA=D0=B0=D0=B1=D0=B8=D0=BD=D0=B5?= =?UTF-8?q?=D1=82=D0=BE=D0=B2=20=D0=B2=20=D1=81=D0=BE=D0=BE=D1=82=D0=B2?= =?UTF-8?q?=D0=B5=D1=82=D1=81=D1=82=D0=B2=D0=B8=D0=B5=20=D1=81=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=82=D0=BE=D1=82=D0=B8=D0=BF=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/client_account/models.py | 4 +- src/accounts/client_account/routers.py | 55 ++++--- src/accounts/client_account/schemas.py | 55 +++++-- src/accounts/client_account/services.py | 172 +++++++++++++++------ src/accounts/developer_account/routers.py | 17 +- src/accounts/developer_account/services.py | 22 +-- src/accounts/employee_account/routers.py | 51 ++++-- src/accounts/employee_account/services.py | 53 +++---- 8 files changed, 268 insertions(+), 161 deletions(-) diff --git a/src/accounts/client_account/models.py b/src/accounts/client_account/models.py index bf98317..2bd79da 100644 --- a/src/accounts/client_account/models.py +++ b/src/accounts/client_account/models.py @@ -8,12 +8,12 @@ class Client(Base): __tablename__ = 'client' id = Column(Integer, primary_key=True, index=True, unique=True) - name = Column(String, unique=True, nullable=False) # TODO can be unique? + name = Column(String, nullable=False) + avatar = Column(String, nullable=True) is_active = Column(Boolean, default=False, nullable=False) date_create = Column(DateTime(timezone=True), server_default=sql.func.now()) date_block = Column(DateTime, default=None, nullable=True) owner_id = Column(String, ForeignKey('user.id'), nullable=False) - # avatar clients = Client.__table__ diff --git a/src/accounts/client_account/routers.py b/src/accounts/client_account/routers.py index 735635b..5fa8824 100644 --- a/src/accounts/client_account/routers.py +++ b/src/accounts/client_account/routers.py @@ -1,20 +1,22 @@ -from typing import List +from typing import List, Union -from fastapi import APIRouter, Depends, status, Request -from .schemas import ClientDB, Client, ClientCreate, ClientUpdate, ClientAndOwnerCreate -from .services import get_clients, get_client, add_client, update_client, add_owner, get_client_owner, update_owner +from fastapi import APIRouter, Depends, status, Request, HTTPException +from .schemas import ClientDB, Client, ClientCreate, ClientUpdate, ClientAndOwnerCreate, ClientsPage, ClientPage, \ + DevClientPage +from .services import get_clients, get_client, add_client, update_client, add_owner, get_client_owner, update_client_owner, \ + get_clients_page, get_client_page, block_client, get_dev_client_page, get_client_info from src.users.models import UserTable -from src.users.logic import developer_user, any_user, get_client_users_with_superuser, get_owner_with_superuser -from src.reference_book.api.licence import client_licences_router +from src.users.logic import developer_user, any_user, get_client_users_with_superuser, get_owner_with_superuser, \ + get_client_users from ..employee_account.routers import employee_router from ...users.schemas import UserCreate client_router = APIRouter() -@client_router.get("/", response_model=List[ClientDB], status_code=status.HTTP_200_OK) +@client_router.get("/", response_model=ClientsPage, status_code=status.HTTP_200_OK) async def clients_list(user: UserTable = Depends(developer_user)): - return await get_clients() + return await get_clients_page() @client_router.post("/", response_model=ClientDB, status_code=status.HTTP_201_CREATED) @@ -23,35 +25,32 @@ async def create_client(item: ClientAndOwnerCreate, user: UserTable = Depends(de return new_client -@client_router.get("/{id}", response_model=Client, status_code=status.HTTP_200_OK) +@client_router.get("/{id}", response_model=Union[ClientPage, DevClientPage], status_code=status.HTTP_200_OK) async def client(id: int, user: UserTable = Depends(any_user)): - user = await get_client_users_with_superuser(id, user) - return await get_client(id) + if user.is_superuser: + return await get_dev_client_page(id) + elif await get_client_users(id, user): + return await get_client_page(id) + else: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) -@client_router.patch("/{id}", response_model=ClientDB, status_code=status.HTTP_201_CREATED) +@client_router.get("/{id}/info", response_model=Client, status_code=status.HTTP_200_OK) +async def client_info(id: int, user: UserTable = Depends(any_user)): + user = await get_owner_with_superuser(id, user) + return await get_client_info(id) + + +@client_router.patch("/{id}/info", response_model=ClientDB, status_code=status.HTTP_201_CREATED) async def update_client_by_id(id: int, item: ClientUpdate, user: UserTable = Depends(any_user)): # TODO разделить изменение аватарки владельцем и изменение владельца владельцем или разработчиком user = await get_owner_with_superuser(id, user) return await update_client(id, item) -@client_router.get("/{id}/owner", status_code=status.HTTP_200_OK) -async def owner(id: int, user: UserTable = Depends(any_user)): - user = await get_client_users_with_superuser(id, user) - return await get_client_owner(id) - - -@client_router.post("/{id}/owner", status_code=status.HTTP_201_CREATED) -async def create_owner(id: int, item: UserCreate, user: UserTable = Depends(developer_user)): - return await add_owner(id, item) - - -@client_router.patch("/{id}/owner", status_code=status.HTTP_201_CREATED) -async def change_owner(id: int, item: ClientUpdate, user: UserTable = Depends(developer_user)): - user = await get_owner_with_superuser(id, user) - return await update_owner(id, item.owner_id) +@client_router.patch("/{id}/block", response_model=ClientDB, status_code=status.HTTP_201_CREATED) +async def block_client_by_id(id: int, user: UserTable = Depends(developer_user)): + return await block_client(id) -client_router.include_router(client_licences_router) client_router.include_router(employee_router) diff --git a/src/accounts/client_account/schemas.py b/src/accounts/client_account/schemas.py index 3f4a892..74d91a2 100644 --- a/src/accounts/client_account/schemas.py +++ b/src/accounts/client_account/schemas.py @@ -2,19 +2,20 @@ from typing import Optional, List from pydantic import BaseModel, EmailStr, validator -from pydantic.types import UUID4 -from ...reference_book.schemas import LicenceDB, SoftwareDB -from ...users.schemas import UserDB, generate_pwd +from ...reference_book.schemas import LicenceDB, SoftwareDB, Licence +from ...users.schemas import UserDB, generate_pwd, Employee, EmployeeList class ClientBase(BaseModel): name: str + avatar: Optional[str] class ClientCreate(ClientBase): name: Optional[str] owner_id: Optional[str] + avatar: Optional[str] class ClientAndOwnerCreate(ClientCreate): @@ -23,9 +24,13 @@ class ClientAndOwnerCreate(ClientCreate): patronymic: Optional[str] email: EmailStr password: str = generate_pwd() + avatar: Optional[str] + owner_avatar: Optional[str] is_active: Optional[bool] = True is_superuser: Optional[bool] = False is_verified: Optional[bool] = False + owner_licence: int + licences_list: List[int] @validator('password') def valid_password(cls, v: str): @@ -36,24 +41,50 @@ def valid_password(cls, v: str): class ClientUpdate(ClientBase): # TODO доработать изменение заказчика name: Optional[str] - # avatar - pass + owner_id: Optional[str] -class Client(ClientBase): +class ClientDB(ClientBase): id: int is_active: bool date_create: datetime date_block: Optional[datetime] + owner_id: str + + +class ClientShort(ClientDB): + is_active: bool owner: UserDB - employees: List[UserDB] - licences: List[LicenceDB] - software: List[SoftwareDB] + count_employees: int = 0 -class ClientDB(ClientBase): +class Client(ClientBase): id: int is_active: bool date_create: datetime - date_block: Optional[datetime] - owner_id: str + owner: Employee + licences: List[Licence] + + +class ClientPage(ClientBase): + client: Client + employees_list: List[EmployeeList] + licences_list: List[Licence] + + +class DevClientPage(ClientBase): + client: Client + employees_list: List[Employee] = [] + software_list: List[SoftwareDB] + + +class ClientsPage(ClientBase): + clients_list: List[ClientShort] + licences_list: List[LicenceDB] + + +class EmployeePage(BaseModel): + employees: Employee + client: Client + licences: List[Licence] + diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index 93bfe80..b57e885 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -1,96 +1,166 @@ -import uuid from datetime import datetime -from typing import Dict, Any +from typing import List, Optional from fastapi import HTTPException, status from fastapi_users.router import ErrorCode from pydantic.types import UUID4 -from .schemas import ClientCreate, ClientUpdate, ClientAndOwnerCreate -from ..employee_account.services import update_employee +from .schemas import ClientCreate, ClientUpdate, ClientAndOwnerCreate, ClientsPage, ClientShort, ClientPage, ClientDB, \ + Client, DevClientPage from ...db.db import database from .models import clients +from ...desk.models import appeals from ...errors import Errors -from ...reference_book.models import licences, softwares -from ...users.logic import all_users, get_or_404 +from ...reference_book.schemas import LicenceDB +from ...reference_book.services import get_licences, add_client_licence, get_client_licences, get_software_db_list, \ + add_employee_licence, get_employee_licence +from ...users.logic import all_users, get_or_404, pre_update_user from ...users.models import users -from ...users.schemas import UserCreate, OwnerCreate, UserUpdate -from ...service import check_dict, send_mail +from ...users.schemas import UserCreate, OwnerCreate, UserUpdate, Employee, UserDB, EmployeeList +from ...service import send_mail -async def get_clients(): +async def get_count_appeals(employee_id: UUID4) -> int: + query = appeals.select().where(appeals.c.author_id == employee_id) + result = await database.fetch_all(query=query) + return len([dict(appeal) for appeal in result]) + + +async def get_employees(client_id: int) -> List[EmployeeList]: + result = await database.fetch_all(users.select().where(users.c.client_id == client_id)) + employees_list = [] + for employee in result: + employee = dict(employee) + licence: LicenceDB = await get_employee_licence(UUID4(employee["id"])) + count_appeals: int = await get_count_appeals(UUID4(employee["id"])) + employees_list.append(EmployeeList(**dict({**employee, "licence": licence, "count_appeals": count_appeals}))) + return employees_list + + +async def get_count_employees(client_id: int) -> int: + result = await database.fetch_all(users.select().where(users.c.client_id == client_id)) + # result = [dict(employee) for employee in employees_data] + return len(result) # TODO проверить не будет ли ошибки + + +async def get_clients() -> List[ClientShort]: result = await database.fetch_all(clients.select()) - return [dict(client) for client in result] + clients_list = [] + for client in result: + client = dict(client) + count_employees = await get_count_employees(client["id"]) + clients_list.append(ClientShort(**dict({**client, "count_employees": count_employees}))) + return clients_list + + +async def get_clients_page() -> ClientsPage: + clients_list = await get_clients() + licences_list = await get_licences() + return ClientsPage(**dict({"clients_list": clients_list, "licences_list": licences_list})) -async def get_client(id: int): - query = clients.select().where(clients.c.id == id) +async def get_client_info(client_id: int) -> Client: + client = await get_client(client_id) + return client + + +async def get_client_page(client_id: int) -> ClientPage: + client = await get_client(client_id) + employees_list = await get_employees(client_id) + licences_list = await get_client_licences(client_id) + return ClientPage(**dict({**client, + "employees_list": employees_list, + "licences_list": licences_list})) + + +async def get_dev_client_page(client_id: int) -> DevClientPage: + client = await get_client(client_id) + employees_list = await get_employees(client_id) + software_list = await get_software_db_list() + return DevClientPage(**dict({**client, + "employees_list": employees_list, + "software_list": software_list})) + + +async def get_client(client_id: int) -> Optional[Client]: + query = clients.select().where(clients.c.id == client_id) result = await database.fetch_one(query=query) - if result: - result = dict(result) - owner_data = await database.fetch_one(query=users.select().where(users.c.id == result["owner_id"])) - owner = await check_dict(owner_data) - employee_data = await database.fetch_all(query=users.select().where(users.c.client_id == id)) - employees = [dict(employee) for employee in employee_data] - licence_data = await database.fetch_all(query=licences.select().where(licences.c.client_id == id)) - licences_dict = [dict(licence) for licence in licence_data] - software = [await database.fetch_one( - softwares.select().where(softwares.c.id == licence["software_id"]) - ) for licence in licence_data] - return {**result, - "owner": owner, - "employees": employees, - "licences": licences_dict, - "software": software} - - -async def get_client_db(client_id: int): + if result is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=Errors.CLIENT_NOT_FOUND) + owner = await get_client_owner(client_id) + licences_list = await get_client_licences(client_id) + return Client(**dict({**dict(result), + "owner": owner, + "licences": licences_list})) + + +async def get_client_db(client_id: int) -> Optional[ClientDB]: query = clients.select().where(clients.c.id == client_id) client = await database.fetch_one(query=query) if client: - return dict(client) + return ClientDB(**dict(client)) return None -async def add_client(data: ClientAndOwnerCreate): +async def add_client(data: ClientAndOwnerCreate) -> Optional[ClientDB]: client = ClientCreate(**data.dict()) query = clients.insert().values({**client.dict(), "is_active": False, "owner_id": "undefined"}) - client_id = await database.execute(query) + try: + client_id = await database.execute(query) + except Exception: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=Errors.COMPANY_IS_EXIST, + ) + for licence_id in data.licences_list: + licence = await add_client_licence(client_id, licence_id) owner = OwnerCreate( email=data.email, password=data.password, name=data.owner_name, surname=data.surname, + avatar=data.owner_avatar, client_id=client_id, is_owner=True, - date_reg=datetime.utcnow() + date_reg=datetime.utcnow(), ) owner = await add_owner(client_id, owner) + owner_licence = await add_employee_licence(owner.id, data.owner_licence) new_client = await get_client_db(client_id) return new_client -async def update_client(id: int, client: ClientUpdate): # TODO проверить работу обновления +async def update_client(id: int, client: ClientUpdate) -> Optional[ClientDB]: # TODO проверить работу обновления client_dict = client.dict() if "owner_id" in client_dict: client_dict["owner_id"] = str(client_dict["owner_id"]) query = clients.update().where(clients.c.id == id).values(**client_dict) client_id = await database.execute(query) updated_client = await get_client_db(client_id) - print(updated_client) return updated_client -async def activate_client(id: int): +async def activate_client(id: int) -> Optional[ClientDB]: current_client = await database.fetch_one(query=clients.select().where(clients.c.id == id)) if current_client: current_client = dict(current_client) current_client["is_active"] = True await database.execute(query=clients.update().where(clients.c.id == id).values(**current_client)) - return None + return current_client + + +async def block_client(id: int) -> Optional[ClientDB]: + current_client = await database.fetch_one(query=clients.select().where(clients.c.id == id)) + if current_client: + current_client = dict(current_client) + current_client["is_active"] = False + await database.execute(query=clients.update().where(clients.c.id == id).values(**current_client)) + return current_client -async def get_client_owner(client_id: int): +async def get_client_owner(client_id: int) -> Employee: query = users.select().where((users.c.client_id == client_id) & (users.c.is_owner is True)) owner = await database.fetch_one(query=query) if owner is None: @@ -98,24 +168,24 @@ async def get_client_owner(client_id: int): status_code=status.HTTP_404_NOT_FOUND, detail=Errors.USER_NOT_FOUND ) - return dict(owner) + owner = dict(owner) + licence: LicenceDB = await get_employee_licence(UUID4(owner["id"])) + return Employee(**dict({**owner, "licence": licence})) -async def update_owner(client_id: int, new_owner_id: UUID4): - client = await database.fetch_one(clients.select().where(clients.c.id == client_id)) +async def update_client_owner(client_id: int, new_owner_id: UUID4) -> Optional[UserDB]: + client = await get_client_db(client_id) if client: - client = dict(client) - if client["owner_id"] == "undefined": - # client["owner_id"] = new_owner_id - new_client = ClientCreate(name=client["name"], owner_id=str(new_owner_id)) + if client.owner_id == "undefined": + new_client = ClientUpdate(name=client.name, owner_id=str(new_owner_id)) await update_client(client_id, new_client) new_owner = await get_or_404(new_owner_id) else: update_old = UserUpdate(is_owner=False) update_new = UserUpdate(is_owner=True) - new_client = ClientCreate(**client, owner_id=new_owner_id) - old_owner = await update_employee(client["owner_id"], update_old) - new_owner = await update_employee(new_owner_id, update_new) + new_client = ClientUpdate(**dict(client), owner_id=new_owner_id) + old_owner = await pre_update_user(UUID4(client.owner_id), update_old) + new_owner = await pre_update_user(new_owner_id, update_new) client = await update_client(client_id, new_client) return new_owner return None @@ -133,5 +203,5 @@ async def add_owner(client_id: int, owner: OwnerCreate): message = f"Добро пожаловать в UDV Service Desk!\n\nВаш логин в системе: {owner.email}\nВаш пароль: {owner.password}" await send_mail(owner.email, "Вы зарегистрированы в системе", message) - updated_owner = await update_owner(client_id, created_owner.id) + updated_owner = await update_client_owner(client_id, created_owner.id) return updated_owner diff --git a/src/accounts/developer_account/routers.py b/src/accounts/developer_account/routers.py index a99ef1e..5090d13 100644 --- a/src/accounts/developer_account/routers.py +++ b/src/accounts/developer_account/routers.py @@ -3,15 +3,15 @@ from fastapi import APIRouter, Depends, status, Response from pydantic.types import UUID4 -from .services import get_developer, add_developer, update_developer, delete_developer +from .services import get_developer, add_developer, delete_developer from src.users.models import UserTable -from src.users.logic import developer_user, get_developers -from src.users.schemas import UserDB, UserCreate, UserUpdate +from src.users.logic import developer_user, get_developers, pre_update_user, change_pwd +from src.users.schemas import UserDB, UserCreate, UserUpdate, DeveloperList developer_router = APIRouter() -@developer_router.get("/", response_model=List[UserDB], status_code=status.HTTP_200_OK) +@developer_router.get("/", response_model=List[DeveloperList], status_code=status.HTTP_200_OK) async def developers_list(user: UserTable = Depends(developer_user)): return await get_developers() @@ -22,13 +22,18 @@ async def developer(id: UUID4, user: UserTable = Depends(developer_user)): @developer_router.post("/") -async def create_developer(item: UserCreate): +async def create_developer(item: UserCreate, user: UserTable = Depends(developer_user)): return await add_developer(item) @developer_router.patch("/{id:uuid}", response_model=UserDB, status_code=status.HTTP_201_CREATED) async def update_developer_by_id(id: UUID4, item: UserUpdate, user: UserTable = Depends(developer_user)): - return await update_developer(id, item) + return await pre_update_user(id, item) + + +@developer_router.patch("/{id:uuid}/pwd", response_model=UserDB, status_code=status.HTTP_201_CREATED) +async def change_dev_pwd(id: UUID4, new_pwd: str, user: UserTable = Depends(developer_user)): + return await change_pwd(id, new_pwd) @developer_router.delete("/{id:uuid}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) diff --git a/src/accounts/developer_account/services.py b/src/accounts/developer_account/services.py index 2b4714b..70a8d18 100644 --- a/src/accounts/developer_account/services.py +++ b/src/accounts/developer_account/services.py @@ -1,4 +1,4 @@ -from datetime import datetime +from typing import Optional from fastapi import HTTPException, status from fastapi_users.router import ErrorCode @@ -6,20 +6,20 @@ from src.db.db import database from src.service import send_mail -from src.users.logic import all_users, delete_user, update_user +from src.users.logic import all_users, delete_user, pre_update_user from src.users.models import users -from src.users.schemas import UserCreate, DeveloperCreate, UserUpdate +from src.users.schemas import UserCreate, DeveloperCreate, UserUpdate, UserDB -async def get_developer(id: UUID4): - developer = await database.fetch_one(users.select().where((users.c.is_superuser is True) & (users.c.id == id))) +async def get_developer(id: UUID4) -> Optional[UserDB]: + query = users.select().where((users.c.is_superuser is True) & (users.c.id == id)) + developer = await database.fetch_one(query=query) if developer: - developer = dict(developer) - return developer + return UserDB(**dict(developer)) return None -async def add_developer(user: UserCreate): +async def add_developer(user: UserCreate) -> UserDB: developer = DeveloperCreate(**user.dict()) try: created_developer = await all_users.create_user(developer, safe=False) @@ -34,11 +34,5 @@ async def add_developer(user: UserCreate): return created_developer -async def update_developer(id: UUID4, item: UserUpdate): - update_dict = item.dict(exclude_unset=True) - updated_developer = await update_user(id, update_dict) - return updated_developer - - async def delete_developer(id: UUID4): await delete_user(id) diff --git a/src/accounts/employee_account/routers.py b/src/accounts/employee_account/routers.py index 781b808..8ad77f9 100644 --- a/src/accounts/employee_account/routers.py +++ b/src/accounts/employee_account/routers.py @@ -1,54 +1,71 @@ -from typing import List +from typing import Optional -from fastapi import APIRouter, Request, status, Depends, HTTPException, Response +from fastapi import APIRouter, status, Depends, HTTPException, Response from pydantic.types import UUID4 -from .services import add_employee, count_allowed_employees, get_employees, \ - get_employee, update_employee, delete_employee, block_employee +from .services import add_employee, get_count_allowed_employees, get_employee, delete_employee, block_employee from src.errors import Errors -from src.users.logic import get_owner, any_user, get_client_users_with_superuser +from src.users.logic import get_owner, any_user, get_client_users_with_superuser, pre_update_user, change_pwd from src.users.models import UserTable -from src.users.schemas import UserCreate, UserDB, UserUpdate +from src.users.schemas import UserDB, PreEmployeeCreate, EmployeeUpdate +from ..client_account.schemas import EmployeePage +from ..client_account.services import update_client_owner employee_router = APIRouter() -@employee_router.get("/{id}/employees", response_model=List[UserDB], status_code=status.HTTP_200_OK) -async def employees_list(id: int, user: UserTable = Depends(any_user)): - user = await get_client_users_with_superuser(id, user) - return await get_employees(id) +# @employee_router.get("/{id}/employees", response_model=List[UserDB], status_code=status.HTTP_200_OK) +# async def employees_list(id: int, user: UserTable = Depends(any_user)): +# user = await get_client_users_with_superuser(id, user) +# return await get_employees(id) -@employee_router.get("/{id}/employees/{pk}", response_model=UserDB, status_code=status.HTTP_200_OK) +@employee_router.get("/{id}/employees/{pk}", response_model=Optional[EmployeePage], status_code=status.HTTP_200_OK) async def employee(id: int, pk: UUID4, user: UserTable = Depends(any_user)): user = await get_client_users_with_superuser(id, user) return await get_employee(id, pk) @employee_router.post("/{id}/employees", response_model=UserDB, status_code=status.HTTP_201_CREATED) -async def create_employee(id: int, item: UserCreate, user: UserTable = Depends(any_user)): +async def create_employee(id: int, item: PreEmployeeCreate, user: UserTable = Depends(any_user)): user = await get_owner(id, user) - if await count_allowed_employees(id): + if await get_count_allowed_employees(id) > 0: return await add_employee(id, item) else: raise HTTPException( status_code=status.HTTP_409_CONFLICT, - detail=Errors.NO_VACANCIES_UNDER_LICENCES, + detail=Errors.CLIENT_HAS_NOT_FREE_VACANCIES, ) @employee_router.patch("/{id}/employees/{pk}", response_model=UserDB, status_code=status.HTTP_201_CREATED) -async def update_employee_by_id(id: int, pk: UUID4, item: UserUpdate, user: UserTable = Depends(any_user)): +async def update_employee_by_id(id: int, pk: UUID4, item: EmployeeUpdate, user: UserTable = Depends(any_user)): + user = await get_owner(id, user) + return await pre_update_user(pk, item) + + +@employee_router.post("/{id}/employees/{pk}/make_owner", response_model=UserDB, status_code=status.HTTP_201_CREATED) +async def block_employee_by_id(id: int, pk: UUID4, user: UserTable = Depends(any_user)): + # TODO отправляет post or patch? user = await get_owner(id, user) - return await update_employee(pk, item) + return await update_client_owner(id, pk) -@employee_router.patch("/{id}/employees/{pk}/block", response_model=UserDB, status_code=status.HTTP_201_CREATED) +@employee_router.post("/{id}/employees/{pk}/block", response_model=UserDB, status_code=status.HTTP_201_CREATED) async def block_employee_by_id(id: int, pk: UUID4, user: UserTable = Depends(any_user)): + # TODO отправляет post or patch? user = await get_owner(id, user) return await block_employee(pk) +@employee_router.patch("/{id:uuid}/pwd", response_model=UserDB, status_code=status.HTTP_201_CREATED) +async def change_employee_pwd(id: UUID4, new_pwd: str, user: UserTable = Depends(any_user)): + if user.id == id: + return await change_pwd(id, new_pwd) + else: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) + + @employee_router.delete("/{id}/employees/{pk}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) async def delete_employee_by_id(id: int, pk: UUID4, user: UserTable = Depends(any_user)): user = await get_owner(id, user) diff --git a/src/accounts/employee_account/services.py b/src/accounts/employee_account/services.py index e45fa04..16e3983 100644 --- a/src/accounts/employee_account/services.py +++ b/src/accounts/employee_account/services.py @@ -1,45 +1,39 @@ -from datetime import datetime +from typing import Optional -from fastapi import HTTPException, status, Request +from fastapi import HTTPException, status from fastapi_users.router import ErrorCode from pydantic.types import UUID4 +from src.accounts.client_account.schemas import EmployeePage +from src.accounts.client_account.services import get_client, get_employees from src.db.db import database -from src.reference_book.models import licences +from src.reference_book.services import get_client_licences, add_employee_licence from src.service import send_mail from src.users.logic import all_users, update_user, delete_user from src.users.models import users -from src.users.schemas import UserCreate, EmployeeCreate, UserUpdate +from src.users.schemas import EmployeeCreate, PreEmployeeCreate, UserDB -async def get_employees(id: int): - employees_data = await database.fetch_all(users.select().where(users.c.client_id == id)) - result = [dict(employee) for employee in employees_data] - return result - - -async def get_employee(id: int, pk: UUID4): - employee = await database.fetch_one(users.select().where((users.c.client_id == id) & (users.c.id == pk))) +async def get_employee(client_id: int, pk: UUID4) -> Optional[EmployeePage]: + employee = await database.fetch_one(users.select().where((users.c.client_id == client_id) & (users.c.id == pk))) if employee: employee = dict(employee) - return employee + client = await get_client(client_id) + client_licences = client.licences + return EmployeePage(**dict({**employee, "client": client, "licences": client_licences})) return None -async def count_allowed_employees(client_id: int): - count = 0 - licence_data = await database.fetch_all(query=licences.select().where(licences.c.client_id == client_id)) - licences_dict = [dict(licence) for licence in licence_data] - for licence in licences_dict: - count += licence["count_members"] +async def get_count_allowed_employees(client_id: int) -> int: + count_allowed_employees = 0 + client_licences = await get_client_licences(client_id) + for licence in client_licences: + count_allowed_employees += licence.count_members + client_employees = await get_employees(client_id) + return count_allowed_employees - len(client_employees) - query = users.select().where((users.c.client_id == client_id) & (users.c.is_active is True)) - employee_data = await database.fetch_all(query=query) - employee_dict = [dict(employee) for employee in employee_data] - return count - len(employee_dict) - -async def add_employee(id: int, user: UserCreate): +async def add_employee(id: int, user: PreEmployeeCreate) -> UserDB: user.client_id = id user.is_owner = False employee = EmployeeCreate(**user.dict()) @@ -50,17 +44,14 @@ async def add_employee(id: int, user: UserCreate): status_code=status.HTTP_400_BAD_REQUEST, detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, ) + licence_id = user.licence_id + licence = await add_employee_licence(created_user.id, licence_id) + message = f"Добро пожаловать в UDV Service Desk!\n\nВаш логин в системе: {user.email}\nВаш пароль: {user.password}" await send_mail(user.email, "Вы зарегистрированы в системе", message) return created_user -async def update_employee(pk: UUID4, item: UserUpdate): - update_dict = item.dict(exclude_unset=True) - updated_employee = await update_user(pk, update_dict) - return updated_employee - - async def delete_employee(pk: UUID4): await delete_user(pk) From 65c1cc2429e1791a39cab6b7e0f8e7e80f3975dd Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sun, 30 May 2021 21:28:48 +0500 Subject: [PATCH 40/55] =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=B2=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82=D0=B2=D0=B8=D0=B5=20?= =?UTF-8?q?=D1=81=20=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D1=82=D0=B8=D0=BF=D0=BE?= =?UTF-8?q?=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/errors.py | 20 ++++++++++++- src/users/logic.py | 70 ++++++++++++++++++++++++++++++++++++++------ src/users/models.py | 2 +- src/users/routes.py | 11 +++++-- src/users/schemas.py | 36 ++++++++++++++++++----- 5 files changed, 119 insertions(+), 20 deletions(-) diff --git a/src/errors.py b/src/errors.py index 8d48ba2..ca24213 100644 --- a/src/errors.py +++ b/src/errors.py @@ -1,6 +1,24 @@ class Errors: - NO_VACANCIES_UNDER_LICENCES = "NO_VACANCIES_UNDER_LICENCES" + CLIENT_HAS_NOT_FREE_VACANCIES = "CLIENT_HAS_NOT_FREE_VACANCIES" USER_NOT_FOUND = "USER_NOT_FOUND" IMPOSSIBLE_DELETE_OWNER = "IMPOSSIBLE_DELETE_OWNER" USER_CAN_NOT_CHANGE_STATUS = "USER_CAN_NOT_CHANGE_STATUS_ON_THE_NEXT" APPEAL_IS_CLOSED = "APPEAL_IS_CLOSED" + COMPANY_IS_EXIST = "COMPANY_IS_EXIST" + + USER_HAS_ANOTHER_LICENCE = "USER_HAS_ANOTHER_LICENCE" + LICENCE_IS_FULL = "LICENCE_IS_FULL" + CLIENT_HAS_THIS_LICENCE = "CLIENT_HAS_THIS_LICENCE" + + MODULE_IS_EXIST = "MODULE_IS_EXIST" + SOFTWARE_IS_EXIST = "SOFTWARE_IS_EXIST" + LICENCE_IS_EXIST = "LICENCE_IS_EXIST" + + MODULE_IS_NOT_EXIST = "MODULE_IS_NOT_EXIST" + SOFTWARE_IS_NOT_EXIST = "SOFTWARE_IS_NOT_EXIST" + LICENCE_IS_NOT_EXIST = "LICENCE_IS_NOT_EXIST" + + MODULE_FOR_THIS_SOFTWARE_IS_EXIST = "MODULE_FOR_THIS_SOFTWARE_IS_EXIST" + MODULE_FOR_THIS_SOFTWARE_IS_NOT_EXIST = "MODULE_FOR_THIS_SOFTWARE_IS_NOT_EXIST" + + CLIENT_NOT_FOUND = "CLIENT_NOT_FOUND" diff --git a/src/users/logic.py b/src/users/logic.py index a8f9b76..47009ec 100644 --- a/src/users/logic.py +++ b/src/users/logic.py @@ -4,15 +4,17 @@ from fastapi_users.password import get_password_hash from ..config import SECRET -from .schemas import User, UserCreate, UserUpdate, UserDB +from .schemas import User, UserCreate, UserUpdate, UserDB, DeveloperList, generate_pwd, EmployeeUpdate from .models import user_db, UserTable, users from src.db.db import database from src.errors import Errors -from typing import Dict, Any +from typing import Dict, Any, List, Optional from pydantic.types import UUID4 -# from requests import Request - +from pydantic import EmailStr +from ..desk.models import appeals +from ..reference_book.services import update_employee_licence +from ..service import send_mail auth_backends = [] @@ -74,12 +76,21 @@ async def after_verification(user: UserDB, request: Request): print(f"{user.id} is now verified.") -async def get_developers(): +async def get_count_dev_appeals(developer_id: UUID4): + query = appeals.select().where(appeals.c.responsible_id == developer_id) + result = await database.fetch_all(query=query) + return len(result) # TODO проверить не будет ли ошибки на len(None) + + +async def get_developers() -> List[DeveloperList]: query = users.select().where(users.c.is_superuser is True) result = await database.fetch_all(query=query) - if result: - return [dict(developer) for developer in result] - return [] + developers = [] + for developer in result: + developer = dict(developer) + count_appeals = await get_count_dev_appeals(developer["id"]) + developers.append(DeveloperList(**dict({**developer, "count_appeals": count_appeals}))) + return developers async def get_or_404(id: UUID4) -> UserDB: @@ -91,7 +102,21 @@ async def get_or_404(id: UUID4) -> UserDB: return user -async def update_user(id: UUID4, update_dict: Dict[str, Any]): +async def get_user(id: UUID4) -> Optional[UserDB]: + user = await database.fetch_one(query=users.select().where(users.c.id == id)) + return user + + +async def pre_update_user(id: UUID4, item: EmployeeUpdate) -> UserDB: + if item.licence_id: + licence = await update_employee_licence(id, item.licence_id) + update_employee = UserUpdate(**dict(item)) + update_dict = update_employee.dict(exclude_unset=True) + updated_developer = await update_user(id, update_dict) + return updated_developer + + +async def update_user(id: UUID4, update_dict: Dict[str, Any]) -> UserDB: user = await get_or_404(id) user = {**user} for field in update_dict: @@ -110,3 +135,30 @@ async def delete_user(id: UUID4): user = await get_or_404(id) await user_db.delete(user) return None + + +async def get_user_by_email(email: EmailStr) -> UserDB: + user = await database.fetch_one(query=users.select().where(users.c.email == email)) + if user is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.USER_NOT_FOUND) + return user + + +async def change_pwd(id: UUID4, pwd: str) -> UserDB: + if len(pwd) < 6: + raise ValueError('Password should be at least 6 characters') + updated_user = EmployeeUpdate(**dict({"password": pwd})) + return await pre_update_user(id, updated_user) + + +async def get_new_password(email: EmailStr) -> UserDB: + user = await get_user_by_email(email) + pwd = await generate_pwd() + + message = f"Добрый день!\nВаш новый пароль: {pwd}\n" \ + f"Если Вы не меняли пароль обратитесь к администратору или смените его самостоятельно" + await send_mail(user.email, "Вы зарегистрированы в системе", message) + + return await change_pwd(user.id, pwd) diff --git a/src/users/models.py b/src/users/models.py index ccf228a..43ede19 100644 --- a/src/users/models.py +++ b/src/users/models.py @@ -11,7 +11,7 @@ class UserTable(Base, SQLAlchemyBaseUserTable): name = Column(String, nullable=False) surname = Column(String, nullable=False) patronymic = Column(String, nullable=True) - # avatar + avatar = Column(String, nullable=True) is_owner = Column(Boolean, default=False, nullable=True) client_id = Column(Integer, ForeignKey('client.id')) # TODO сделать проверку на существующего владельца клиента date_reg = Column(DateTime(timezone=True), server_default=sql.func.now()) diff --git a/src/users/routes.py b/src/users/routes.py index e42abc5..6f77cb1 100644 --- a/src/users/routes.py +++ b/src/users/routes.py @@ -1,11 +1,13 @@ from fastapi import Depends, Response -from fastapi import APIRouter +from fastapi import APIRouter, status +from pydantic import EmailStr from .schemas import UserDB from ..config import SECRET from src.users.logic import jwt_authentication, all_users, \ - on_after_forgot_password, on_after_reset_password, after_verification, after_verification_request, any_user + on_after_forgot_password, on_after_reset_password, after_verification, after_verification_request, any_user, \ + get_new_password router = APIRouter() @@ -22,6 +24,11 @@ async def me( return user +@router.post("/forgot_password", response_model=UserDB, status_code=status.HTTP_201_CREATED) +async def forgot_password(email: EmailStr): + return get_new_password(email) + + router.include_router( all_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", diff --git a/src/users/schemas.py b/src/users/schemas.py index 9feffc5..1ae0657 100644 --- a/src/users/schemas.py +++ b/src/users/schemas.py @@ -2,9 +2,11 @@ import random from fastapi_users import models -from pydantic import validator +from pydantic import validator, EmailStr from typing import Optional +from src.reference_book.schemas import LicenceDB + def generate_pwd(): symbols_list = "+-/*!&$#?=@<>abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" @@ -19,6 +21,7 @@ class User(models.BaseUser): name: str surname: str patronymic: Optional[str] + avatar: Optional[str] # is_owner: bool # client_id: int # date_block: Optional[datetime] @@ -28,9 +31,10 @@ class UserCreate(models.BaseUserCreate): name: str surname: str patronymic: Optional[str] + avatar: Optional[str] password: str = generate_pwd() is_owner: Optional[bool] = False - client_id: Optional[int] + client_id: Optional[int] = 0 date_reg: datetime = datetime.utcnow() date_block: Optional[datetime] @@ -42,19 +46,20 @@ def valid_password(cls, v: str): class EmployeeCreate(UserCreate, models.BaseUserCreate): - # avatar is_owner: bool = False client_id: int date_reg: datetime = datetime.utcnow() +class PreEmployeeCreate(EmployeeCreate, models.BaseUserCreate): + licence_id: int + + class DeveloperCreate(UserCreate, models.BaseUserCreate): - # avatar date_reg: datetime = datetime.utcnow() class OwnerCreate(UserCreate, models.BaseUserCreate): - # avatar is_owner: bool = True client_id: int date_reg: datetime = datetime.utcnow() @@ -66,11 +71,28 @@ class UserUpdate(User, models.BaseUserUpdate): is_owner: Optional[bool] is_active: Optional[bool] date_block: Optional[datetime] + email: Optional[EmailStr] class UserDB(User, models.BaseUserDB): - is_active: bool = True + is_active: bool # TODO проверить зачем здесь был " = True" is_owner: Optional[bool] client_id: Optional[int] - date_reg: datetime = datetime.utcnow() + date_reg: datetime # TODO проверить можно ли убрать " = datetime.utcnow()" date_block: Optional[datetime] + + +class Employee(UserDB): + licence: Optional[LicenceDB] + + +class EmployeeUpdate(UserUpdate): + licence_id: Optional[int] + + +class EmployeeList(Employee): + count_appeals: int + + +class DeveloperList(User): + count_appeals: int From 3199bfad5a22dfaedcda2e1edde9cb5717d1de5e Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sun, 30 May 2021 21:30:55 +0500 Subject: [PATCH 41/55] =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=BE=D0=B2=D1=83=D1=8E=20=D1=84=D1=83=D0=BD?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D1=8E=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20email?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/service.py b/src/service.py index 371ca3c..b94db5c 100644 --- a/src/service.py +++ b/src/service.py @@ -24,8 +24,4 @@ async def send_mail(to_addr: str, subject: str, msg: str): smtp_obj.login(MAIL_LOGIN, MAIL_PWD) smtp_obj.send_message(multipart_msg) smtp_obj.quit() - print("письмо отправлено") - -if __name__ == '__main__': - print("Hello") - asyncio.run(send_mail("puzanovim@yandex.ru", "HELLO", "Hello Hello")) + print(f"письмо отправлено {to_addr}") From 9e6c15ff8ae75197c161e0c2c9c907a654d1d4cf Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Sun, 30 May 2021 23:08:28 +0500 Subject: [PATCH 42/55] create start developer --- src/app.py | 1 + src/users/logic.py | 31 ++++++++++++++++++++++++++++++- src/users/routes.py | 7 ++++++- src/users/schemas.py | 4 ++-- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/app.py b/src/app.py index 2303e2d..59915c8 100644 --- a/src/app.py +++ b/src/app.py @@ -6,6 +6,7 @@ from .accounts.api import accounts_router from .desk.routes import router as desk_router from .reference_book.api.routes import router as book_router +from .users.logic import create_developer from .users.routes import router as users_routes Base.metadata.create_all(bind=engine) diff --git a/src/users/logic.py b/src/users/logic.py index 47009ec..40f664d 100644 --- a/src/users/logic.py +++ b/src/users/logic.py @@ -1,10 +1,13 @@ +from datetime import datetime + from fastapi import HTTPException, Depends, status, Request from fastapi_users import FastAPIUsers from fastapi_users.authentication import JWTAuthentication from fastapi_users.password import get_password_hash +from fastapi_users.router import ErrorCode from ..config import SECRET -from .schemas import User, UserCreate, UserUpdate, UserDB, DeveloperList, generate_pwd, EmployeeUpdate +from .schemas import User, UserCreate, UserUpdate, UserDB, DeveloperList, generate_pwd, EmployeeUpdate, DeveloperCreate from .models import user_db, UserTable, users from src.db.db import database from src.errors import Errors @@ -137,6 +140,32 @@ async def delete_user(id: UUID4): return None +async def create_developer(): + developer = DeveloperCreate( + email="admin@py.com", + password="123456", + is_active=True, + is_superuser=True, + is_verified=False, + name="admin", + surname="string", + patronymic="string", + avatar="string", + is_owner=False, + client_id=0, + date_reg=datetime.utcnow(), + ) + try: + created_developer = await all_users.create_user(developer, safe=False) + except Exception: + print(Exception) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, + ) + return created_developer + + async def get_user_by_email(email: EmailStr) -> UserDB: user = await database.fetch_one(query=users.select().where(users.c.email == email)) if user is None: diff --git a/src/users/routes.py b/src/users/routes.py index 6f77cb1..440a43d 100644 --- a/src/users/routes.py +++ b/src/users/routes.py @@ -7,7 +7,7 @@ from src.users.logic import jwt_authentication, all_users, \ on_after_forgot_password, on_after_reset_password, after_verification, after_verification_request, any_user, \ - get_new_password + get_new_password, create_developer router = APIRouter() @@ -24,6 +24,11 @@ async def me( return user +@router.get("/create_developer", response_model=UserDB) +async def developer(): + return await create_developer() + + @router.post("/forgot_password", response_model=UserDB, status_code=status.HTTP_201_CREATED) async def forgot_password(email: EmailStr): return get_new_password(email) diff --git a/src/users/schemas.py b/src/users/schemas.py index 1ae0657..fb79285 100644 --- a/src/users/schemas.py +++ b/src/users/schemas.py @@ -75,10 +75,10 @@ class UserUpdate(User, models.BaseUserUpdate): class UserDB(User, models.BaseUserDB): - is_active: bool # TODO проверить зачем здесь был " = True" + is_active: bool = True # TODO проверить зачем здесь был " = True" is_owner: Optional[bool] client_id: Optional[int] - date_reg: datetime # TODO проверить можно ли убрать " = datetime.utcnow()" + date_reg: datetime = datetime.utcnow() # TODO проверить можно ли убрать " = datetime.utcnow()" date_block: Optional[datetime] From 3fdd856ca1f27319e930088e83cd5c18a2e0e0d2 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Mon, 31 May 2021 13:56:37 +0500 Subject: [PATCH 43/55] =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BE=D1=81=D0=BF?= =?UTF-8?q?=D0=BE=D1=81=D0=BE=D0=B1=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D1=81?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/reference_book/api/licence.py | 4 +- src/reference_book/api/routes.py | 2 +- src/reference_book/models.py | 6 +- src/reference_book/schemas.py | 6 +- src/reference_book/services.py | 165 +++++++++++++++++++++++------- 5 files changed, 137 insertions(+), 46 deletions(-) diff --git a/src/reference_book/api/licence.py b/src/reference_book/api/licence.py index 1f773ff..872bf10 100644 --- a/src/reference_book/api/licence.py +++ b/src/reference_book/api/licence.py @@ -11,7 +11,9 @@ @router.get("/", response_model=LicencePage, status_code=status.HTTP_200_OK) async def licence_list(user: UserTable = Depends(developer_user)): - return await get_licence_page() + if user.is_superuser: + return await get_licence_page() + return None @router.get('/{id}', response_model=Licence, status_code=status.HTTP_200_OK) diff --git a/src/reference_book/api/routes.py b/src/reference_book/api/routes.py index 1473269..ea171ba 100644 --- a/src/reference_book/api/routes.py +++ b/src/reference_book/api/routes.py @@ -9,7 +9,7 @@ @router.get('/', status_code=status.HTTP_200_OK) -async def get_reference_book(user: UserTable = Depends(developer_user)): +async def get_reference_book(user: UserTable = Depends(developer_user)): # TODO разобраться с доступом licences = await licence_list(user) modules = await modules_list(user) softwares = await software_list(user) diff --git a/src/reference_book/models.py b/src/reference_book/models.py index d8e0dac..cef1944 100644 --- a/src/reference_book/models.py +++ b/src/reference_book/models.py @@ -18,14 +18,13 @@ class Module(Base): id = Column(Integer, primary_key=True, index=True, unique=True) name = Column(String, nullable=False) - software_id = Column(Integer, ForeignKey('software.id'), nullable=False) # TODO сделать проверку на ключ > 0 class Software(Base): __tablename__ = 'software' id = Column(Integer, primary_key=True, index=True, unique=True) - name = Column(String, unique=True, nullable=False) # TODO сделать проверку на непустую строку + name = Column(String, unique=True, nullable=False) class EmployeeLicence(Base): @@ -34,14 +33,13 @@ class EmployeeLicence(Base): id = Column(Integer, primary_key=True, index=True, unique=True) employee_id = Column(String, ForeignKey('user.id'), nullable=False, unique=True) licence_id = Column(Integer, ForeignKey('licence.id'), nullable=False) - UniqueConstraint(employee_id, licence_id) class ClientLicence(Base): __tablename__ = 'ClientLicence' id = Column(Integer, primary_key=True, index=True, unique=True) - client_id = Column(String, ForeignKey('client.id'), nullable=False, unique=True) + client_id = Column(String, ForeignKey('client.id'), nullable=False) licence_id = Column(Integer, ForeignKey('licence.id'), nullable=False) UniqueConstraint(client_id, licence_id) diff --git a/src/reference_book/schemas.py b/src/reference_book/schemas.py index ac79f46..fef31f8 100644 --- a/src/reference_book/schemas.py +++ b/src/reference_book/schemas.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import List +from typing import List, Optional from pydantic import BaseModel @@ -42,12 +42,12 @@ class SoftwareDB(SoftwareBase): class Software(SoftwareBase): id: int - modules: List[ModuleDB] = None + modules: List[ModuleDB] class SoftwarePage(BaseModel): software_list: List[Software] - module_list: List[ModuleDB] + modules_list: List[ModuleDB] class LicenceBase(BaseModel): diff --git a/src/reference_book/services.py b/src/reference_book/services.py index 8e8bffa..1e10196 100644 --- a/src/reference_book/services.py +++ b/src/reference_book/services.py @@ -1,9 +1,12 @@ +from fastapi import HTTPException, status from pydantic.types import UUID4 from .schemas import ModuleCreate, LicenceCreate, SoftwareCreate, EmployeeLicenceCreate, EmployeeLicenceUpdate, \ SoftwareUpdate, SoftwareDB, Software, ModuleDB, ModuleUpdate, LicenceDB, Licence, LicenceUpdate, \ EmployeeLicenceDB, SoftwareModulesCreate, SoftwareModulesDB, SoftwarePage, \ SoftwareWithModulesCreate, LicencePage, ClientLicenceDB, ClientLicenceCreate +from ..accounts.client_account.models import clients +from ..accounts.client_account.schemas import ClientDB from ..db.db import database from .models import softwares, modules, licences, employee_licences, software_modules, client_licences from ..errors import Errors @@ -40,22 +43,28 @@ async def get_software(software_id: int) -> Optional[SoftwareDB]: async def get_software_by_name(software_name: str) -> Optional[SoftwareDB]: result = await database.fetch_one(query=softwares.select().where(softwares.c.name == software_name)) - if result is not None: + if result: return SoftwareDB(**dict(result)) return None async def get_software_with_modules(software_id: int) -> Optional[Software]: result = await database.fetch_one(query=softwares.select().where(softwares.c.id == software_id)) - if result is not None: - module = await get_software_modules(software_id) - return Software(**dict({**dict(result), "modules": module})) - return None + if result is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.SOFTWARE_IS_NOT_EXIST + ) + module = await get_software_modules(software_id) + return Software(**dict({**dict(result), "modules": module})) async def add_software(software: SoftwareCreate) -> SoftwareDB: - if get_software_by_name(software.name) is not None: - raise Errors.SOFTWARE_IS_EXIST + if await get_software_by_name(software.name): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=Errors.SOFTWARE_IS_EXIST, + ) query = softwares.insert().values(**software.dict()) software_id = await database.execute(query) return SoftwareDB(**dict({"id": software_id, **software.dict()})) @@ -65,7 +74,7 @@ async def add_software_with_modules(software_with_modules: SoftwareWithModulesCr software = await add_software(SoftwareCreate(name=software_with_modules.name)) modules_list = software_with_modules.modules for module_id in modules_list: - try: # TODO безопасное добавление + try: module = await add_software_module(software.id, module_id) except Exception as e: print(e) @@ -74,7 +83,10 @@ async def add_software_with_modules(software_with_modules: SoftwareWithModulesCr async def update_software(software_id: int, software: SoftwareUpdate) -> SoftwareDB: if get_software(software_id) is None: - raise Errors.SOFTWARE_IS_NOT_EXIST + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.SOFTWARE_IS_NOT_EXIST, + ) query = softwares.update().where(softwares.c.id == software_id).values(**software.dict()) await database.execute(query) return SoftwareDB(**dict({"id": software_id, **software.dict()})) @@ -82,7 +94,10 @@ async def update_software(software_id: int, software: SoftwareUpdate) -> Softwar async def delete_software(software_id: int) -> None: # TODO safe delete if get_software(software_id) is None: - raise Errors.SOFTWARE_IS_NOT_EXIST + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.SOFTWARE_IS_NOT_EXIST, + ) query = softwares.delete().where(softwares.c.id == software_id) await database.execute(query) @@ -99,13 +114,22 @@ async def get_software_module(software_id: int, module_id: int) -> Optional[Soft async def get_software_modules(software_id: int) -> List[ModuleDB]: query = software_modules.select().where(software_modules.c.software_id == software_id) result = await database.fetch_all(query=query) - return [await get_module(software_module.module_id) for software_module in - result] # TODO проверить работу метода (software_module.module_id) + list = [await get_module(software_module.module_id) for software_module in result] + print(list) + return list # TODO проверить работу метода (software_module.module_id) async def add_software_module(software_id: int, module_id: int) -> SoftwareModulesDB: - if await get_software_module(software_id, module_id) is not None: - raise Errors.MODULE_FOR_THIS_SOFTWARE_IS_EXIST + if await get_software_module(software_id, module_id): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=Errors.MODULE_FOR_THIS_SOFTWARE_IS_EXIST, + ) + if await get_module(module_id) is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=Errors.MODULE_IS_NOT_EXIST, + ) software_module = SoftwareModulesCreate(**dict({"software_id": software_id, "module_id": module_id})) query = software_modules.insert().values(software_module.dict()) software_module_id = await database.execute(query) @@ -114,7 +138,10 @@ async def add_software_module(software_id: int, module_id: int) -> SoftwareModul async def delete_software_module(software_id: int, module_id: int) -> None: if await get_software_module(software_id, module_id) is None: - raise Errors.MODULE_FOR_THIS_SOFTWARE_IS_NOT_EXIST + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.MODULE_FOR_THIS_SOFTWARE_IS_NOT_EXIST, + ) query = software_modules.delete(). \ where((software_modules.c.software_id == software_id) & (software_modules.c.module_id == module_id)) await database.execute(query) @@ -122,14 +149,20 @@ async def delete_software_module(software_id: int, module_id: int) -> None: async def get_modules() -> List[ModuleDB]: result = await database.fetch_all(query=modules.select()) - return [ModuleDB(**dict(module)) for module in result] + modules_list = [] + for module in result: + modules_list.append(ModuleDB(**dict(module))) + return modules_list async def get_module(module_id: int) -> Optional[ModuleDB]: result = await database.fetch_one(query=modules.select().where(modules.c.id == module_id)) - if result is not None: - return ModuleDB(**dict(result)) - return None + if result is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.MODULE_IS_NOT_EXIST, + ) + return ModuleDB(**dict(result)) async def get_module_by_name(module_name: str) -> Optional[ModuleDB]: @@ -141,7 +174,10 @@ async def get_module_by_name(module_name: str) -> Optional[ModuleDB]: async def add_module(module: ModuleCreate) -> ModuleDB: if await get_module_by_name(module.name) is not None: - raise Errors.MODULE_IS_EXIST + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=Errors.MODULE_IS_EXIST, + ) query = modules.insert().values(**module.dict()) module_id = await database.execute(query) return ModuleDB(**dict({"id": module_id, **module.dict()})) @@ -149,7 +185,10 @@ async def add_module(module: ModuleCreate) -> ModuleDB: async def update_module(module_id: int, module: ModuleUpdate) -> ModuleDB: if await get_module(module_id) is None: - raise Errors.MODULE_IS_NOT_EXIST + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.MODULE_IS_NOT_EXIST, + ) query = modules.update().where(modules.c.id == module_id).values(**module.dict()) await database.execute(query) return ModuleDB(**dict({"id": module_id, **module.dict()})) @@ -157,11 +196,22 @@ async def update_module(module_id: int, module: ModuleUpdate) -> ModuleDB: async def delete_module(module_id: int) -> None: if await get_module(module_id) is None: - raise Errors.MODULE_IS_NOT_EXIST + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.MODULE_IS_NOT_EXIST, + ) query = modules.delete().where(modules.c.id == module_id) await database.execute(query) +async def get_licences_db() -> List[LicenceDB]: + result = await database.fetch_all(query=licences.select()) + licences_list = [] + for licence in result: + licences_list.append(LicenceDB(**dict(licence))) + return licences_list + + async def get_licences() -> List[Licence]: result = await database.fetch_all(query=licences.select()) licences_list = [] @@ -205,16 +255,26 @@ async def get_licence_by_number(licence_number: int) -> Optional[LicenceDB]: async def add_licence(licence: LicenceCreate) -> LicenceDB: if await get_licence_by_number(licence.number) is not None: - raise Errors.LICENCE_IS_EXIST + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=Errors.LICENCE_IS_EXIST, + ) + if await get_software(licence.software_id) is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.SOFTWARE_IS_NOT_EXIST, + ) query = licences.insert().values(licence.dict()) licence_id = await database.execute(query) - # await activate_client(item["client_id"]) # TODO активация клиента return LicenceDB(**dict({"id": licence_id, **licence.dict()})) async def update_licence(licence_id: int, licence: LicenceUpdate) -> LicenceDB: if await get_licence_db(licence_id) is None: - raise Errors.LICENCE_IS_NOT_EXIST + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.LICENCE_IS_NOT_EXIST, + ) query = licences.update().where(licences.c.id == licence_id).values(**licence.dict()) await database.execute(query) return await get_licence_db(licence_id) @@ -222,7 +282,10 @@ async def update_licence(licence_id: int, licence: LicenceUpdate) -> LicenceDB: async def delete_licence(licence_id: int) -> None: if await get_licence_db(licence_id) is None: - raise Errors.LICENCE_IS_NOT_EXIST + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.LICENCE_IS_NOT_EXIST, + ) query = licences.delete().where(licences.c.id == licence_id) await database.execute(query) @@ -230,7 +293,7 @@ async def delete_licence(licence_id: int) -> None: async def get_count_employee_for_licence_id(licence_id: int) -> int: query = employee_licences.select().where(employee_licences.c.licence_id == licence_id) result = await database.fetch_all(query=query) - if result is not None: + if result: print(result) # TODO посмотреть можно ли просто вывести len() без преобразования return len([dict(licence) for licence in result]) return 0 @@ -244,7 +307,7 @@ async def get_free_vacancy_in_licence(licence_id: int) -> int: return 0 -async def get_employee_licence(employee_id: UUID4) -> Optional[LicenceDB]: +async def get_employee_licence(employee_id: str) -> Optional[LicenceDB]: query = employee_licences.select().where(employee_licences.c.employee_id == employee_id) result = await database.fetch_one(query=query) if result: @@ -254,13 +317,22 @@ async def get_employee_licence(employee_id: UUID4) -> Optional[LicenceDB]: return None -async def add_employee_licence(employee_id: UUID4, licence_id: int) -> EmployeeLicenceDB: - if await get_employee_licence(employee_id) is not None: - raise Errors.USER_HAS_ANOTHER_LICENCE +async def add_employee_licence(employee_id: str, licence_id: int) -> EmployeeLicenceDB: + if await get_employee_licence(employee_id): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=Errors.USER_HAS_ANOTHER_LICENCE, + ) if await get_licence_db(licence_id) is None: - raise Errors.LICENCE_IS_NOT_EXIST + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.LICENCE_IS_NOT_EXIST, + ) if await get_free_vacancy_in_licence(licence_id) <= 0: - raise Errors.LICENCE_IS_FULL + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=Errors.LICENCE_IS_FULL, + ) employee_licence = EmployeeLicenceCreate(**dict({"employee_id": employee_id, "licence_id": licence_id})) query = employee_licences.insert().values(**employee_licence.dict()) employee_licence_id = await database.execute(query) @@ -269,7 +341,10 @@ async def add_employee_licence(employee_id: UUID4, licence_id: int) -> EmployeeL async def update_employee_licence(employee_id: UUID4, licence_id: int) -> EmployeeLicenceDB: if await get_free_vacancy_in_licence(licence_id) <= 0: - raise Errors.LICENCE_IS_FULL + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=Errors.LICENCE_IS_FULL, + ) employee_licence = EmployeeLicenceUpdate(**dict({"licence_id": licence_id})) query = employee_licences.update().where(employee_licences.c.employee_id == employee_id).values( **employee_licence.dict()) @@ -277,7 +352,7 @@ async def update_employee_licence(employee_id: UUID4, licence_id: int) -> Employ return EmployeeLicenceDB(**dict({"id": employee_licence_id, "employee_id": employee_id, **employee_licence.dict()})) -async def get_client_licence(client_id: int, licence_id: int) -> Optional[ClientLicenceDB]: # TODO чекнуть использование данных методов +async def get_client_licence(client_id: int, licence_id: int) -> Optional[ClientLicenceDB]: query = client_licences.select().\ where((client_licences.c.client_id == client_id) & (client_licences.c.licence_id == licence_id)) result = await database.fetch_one(query=query) @@ -302,10 +377,26 @@ async def get_client_licences(client_id: int) -> List[Licence]: async def add_client_licence(client_id: int, licence_id: int) -> ClientLicenceDB: if await get_client_licence(client_id, licence_id) is not None: - raise Errors.CLIENT_HAS_THIS_LICENCE + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=Errors.CLIENT_HAS_THIS_LICENCE, + ) if await get_licence_db(licence_id) is None: - raise Errors.LICENCE_IS_NOT_EXIST + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.LICENCE_IS_NOT_EXIST, + ) client_licence = ClientLicenceCreate(**dict({"client_id": client_id, "licence_id": licence_id})) query = client_licences.insert().values(**client_licence.dict()) employee_licence_id = await database.execute(query) + client = await activate_client(client_id) return ClientLicenceDB(**dict({"id": employee_licence_id, **client_licence.dict()})) + + +async def activate_client(client_id: int) -> Optional[ClientDB]: + current_client = await database.fetch_one(query=clients.select().where(clients.c.id == client_id)) + if current_client: + current_client = dict(current_client) + current_client["is_active"] = True + await database.execute(query=clients.update().where(clients.c.id == client_id).values(**current_client)) + return current_client From bb944daba00baf0647c1e319eea41dd6cec7a8f3 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Mon, 31 May 2021 13:57:42 +0500 Subject: [PATCH 44/55] =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BE=D1=81=D0=BF?= =?UTF-8?q?=D0=BE=D1=81=D0=BE=D0=B1=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=9B?= =?UTF-8?q?=D0=9A=20=D0=B7=D0=B0=D0=BA=D0=B0=D0=B7=D1=87=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=B8=20=D1=80=D0=B0=D0=B7=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D1=87=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/client_account/models.py | 1 - src/accounts/client_account/routers.py | 3 +- src/accounts/client_account/schemas.py | 11 ++-- src/accounts/client_account/services.py | 73 ++++++++++------------ src/accounts/developer_account/routers.py | 17 ++--- src/accounts/developer_account/services.py | 13 ++-- src/errors.py | 1 + src/users/logic.py | 29 +++++++-- src/users/schemas.py | 1 + 9 files changed, 81 insertions(+), 68 deletions(-) diff --git a/src/accounts/client_account/models.py b/src/accounts/client_account/models.py index 2bd79da..71fde65 100644 --- a/src/accounts/client_account/models.py +++ b/src/accounts/client_account/models.py @@ -1,4 +1,3 @@ -from fastapi_users.db.sqlalchemy import GUID from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, sql from ...db.db import Base diff --git a/src/accounts/client_account/routers.py b/src/accounts/client_account/routers.py index 5fa8824..bd0ce0b 100644 --- a/src/accounts/client_account/routers.py +++ b/src/accounts/client_account/routers.py @@ -21,8 +21,7 @@ async def clients_list(user: UserTable = Depends(developer_user)): @client_router.post("/", response_model=ClientDB, status_code=status.HTTP_201_CREATED) async def create_client(item: ClientAndOwnerCreate, user: UserTable = Depends(developer_user)): - new_client = await add_client(item) - return new_client + return await add_client(item) @client_router.get("/{id}", response_model=Union[ClientPage, DevClientPage], status_code=status.HTTP_200_OK) diff --git a/src/accounts/client_account/schemas.py b/src/accounts/client_account/schemas.py index 74d91a2..fc81d70 100644 --- a/src/accounts/client_account/schemas.py +++ b/src/accounts/client_account/schemas.py @@ -9,13 +9,12 @@ class ClientBase(BaseModel): name: str - avatar: Optional[str] + avatar: str class ClientCreate(ClientBase): name: Optional[str] owner_id: Optional[str] - avatar: Optional[str] class ClientAndOwnerCreate(ClientCreate): @@ -42,11 +41,13 @@ def valid_password(cls, v: str): class ClientUpdate(ClientBase): # TODO доработать изменение заказчика name: Optional[str] owner_id: Optional[str] + avatar: Optional[str] class ClientDB(ClientBase): id: int is_active: bool + avatar: str date_create: datetime date_block: Optional[datetime] owner_id: str @@ -66,19 +67,19 @@ class Client(ClientBase): licences: List[Licence] -class ClientPage(ClientBase): +class ClientPage(BaseModel): client: Client employees_list: List[EmployeeList] licences_list: List[Licence] -class DevClientPage(ClientBase): +class DevClientPage(BaseModel): client: Client employees_list: List[Employee] = [] software_list: List[SoftwareDB] -class ClientsPage(ClientBase): +class ClientsPage(BaseModel): clients_list: List[ClientShort] licences_list: List[LicenceDB] diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index b57e885..75d6b98 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -12,15 +12,15 @@ from ...desk.models import appeals from ...errors import Errors from ...reference_book.schemas import LicenceDB -from ...reference_book.services import get_licences, add_client_licence, get_client_licences, get_software_db_list, \ - add_employee_licence, get_employee_licence +from ...reference_book.services import add_client_licence, get_client_licences, get_software_db_list, \ + add_employee_licence, get_employee_licence, get_licences_db from ...users.logic import all_users, get_or_404, pre_update_user from ...users.models import users -from ...users.schemas import UserCreate, OwnerCreate, UserUpdate, Employee, UserDB, EmployeeList +from ...users.schemas import UserCreate, OwnerCreate, Employee, UserDB, EmployeeList, EmployeeUpdate from ...service import send_mail -async def get_count_appeals(employee_id: UUID4) -> int: +async def get_count_appeals(employee_id: str) -> int: query = appeals.select().where(appeals.c.author_id == employee_id) result = await database.fetch_all(query=query) return len([dict(appeal) for appeal in result]) @@ -31,8 +31,8 @@ async def get_employees(client_id: int) -> List[EmployeeList]: employees_list = [] for employee in result: employee = dict(employee) - licence: LicenceDB = await get_employee_licence(UUID4(employee["id"])) - count_appeals: int = await get_count_appeals(UUID4(employee["id"])) + licence: LicenceDB = await get_employee_licence(str(employee["id"])) + count_appeals: int = await get_count_appeals(str(employee["id"])) employees_list.append(EmployeeList(**dict({**employee, "licence": licence, "count_appeals": count_appeals}))) return employees_list @@ -48,14 +48,15 @@ async def get_clients() -> List[ClientShort]: clients_list = [] for client in result: client = dict(client) + owner = await get_client_owner(client["id"]) count_employees = await get_count_employees(client["id"]) - clients_list.append(ClientShort(**dict({**client, "count_employees": count_employees}))) + clients_list.append(ClientShort(**dict({**client, "owner": owner, "count_employees": count_employees}))) return clients_list async def get_clients_page() -> ClientsPage: clients_list = await get_clients() - licences_list = await get_licences() + licences_list = await get_licences_db() return ClientsPage(**dict({"clients_list": clients_list, "licences_list": licences_list})) @@ -77,7 +78,7 @@ async def get_dev_client_page(client_id: int) -> DevClientPage: client = await get_client(client_id) employees_list = await get_employees(client_id) software_list = await get_software_db_list() - return DevClientPage(**dict({**client, + return DevClientPage(**dict({"client": client, "employees_list": employees_list, "software_list": software_list})) @@ -105,7 +106,7 @@ async def get_client_db(client_id: int) -> Optional[ClientDB]: async def add_client(data: ClientAndOwnerCreate) -> Optional[ClientDB]: - client = ClientCreate(**data.dict()) + client = ClientCreate(**dict(data)) query = clients.insert().values({**client.dict(), "is_active": False, "owner_id": "undefined"}) try: client_id = await database.execute(query) @@ -127,41 +128,31 @@ async def add_client(data: ClientAndOwnerCreate) -> Optional[ClientDB]: date_reg=datetime.utcnow(), ) owner = await add_owner(client_id, owner) - owner_licence = await add_employee_licence(owner.id, data.owner_licence) + owner_licence = await add_employee_licence(str(owner.id), data.owner_licence) new_client = await get_client_db(client_id) return new_client -async def update_client(id: int, client: ClientUpdate) -> Optional[ClientDB]: # TODO проверить работу обновления - client_dict = client.dict() - if "owner_id" in client_dict: - client_dict["owner_id"] = str(client_dict["owner_id"]) - query = clients.update().where(clients.c.id == id).values(**client_dict) - client_id = await database.execute(query) +async def update_client(client_id: int, client: ClientUpdate) -> Optional[ClientDB]: # TODO проверить работу обновления + new_client_dict = dict(client) + old_client_dict = dict(await get_client_db(client_id)) + for field in new_client_dict: + if new_client_dict[field]: + old_client_dict[field] = new_client_dict[field] + query = clients.update().where(clients.c.id == client_id).values(**old_client_dict) + updated_client = await database.execute(query) updated_client = await get_client_db(client_id) return updated_client -async def activate_client(id: int) -> Optional[ClientDB]: - current_client = await database.fetch_one(query=clients.select().where(clients.c.id == id)) - if current_client: - current_client = dict(current_client) - current_client["is_active"] = True - await database.execute(query=clients.update().where(clients.c.id == id).values(**current_client)) - return current_client - - -async def block_client(id: int) -> Optional[ClientDB]: - current_client = await database.fetch_one(query=clients.select().where(clients.c.id == id)) - if current_client: - current_client = dict(current_client) - current_client["is_active"] = False - await database.execute(query=clients.update().where(clients.c.id == id).values(**current_client)) - return current_client +async def block_client(client_id: int) -> Optional[ClientDB]: + new_client = ClientUpdate(is_activa=False) + updated_client = await update_client(client_id, new_client) + return updated_client async def get_client_owner(client_id: int) -> Employee: - query = users.select().where((users.c.client_id == client_id) & (users.c.is_owner is True)) + query = users.select().where((users.c.client_id == client_id) & (users.c.is_owner == 1)) owner = await database.fetch_one(query=query) if owner is None: raise HTTPException( @@ -169,21 +160,20 @@ async def get_client_owner(client_id: int) -> Employee: detail=Errors.USER_NOT_FOUND ) owner = dict(owner) - licence: LicenceDB = await get_employee_licence(UUID4(owner["id"])) + licence: LicenceDB = await get_employee_licence(str(owner["id"])) return Employee(**dict({**owner, "licence": licence})) async def update_client_owner(client_id: int, new_owner_id: UUID4) -> Optional[UserDB]: client = await get_client_db(client_id) if client: + new_client = ClientUpdate(owner_id=str(new_owner_id)) if client.owner_id == "undefined": - new_client = ClientUpdate(name=client.name, owner_id=str(new_owner_id)) - await update_client(client_id, new_client) + client = await update_client(client_id, new_client) new_owner = await get_or_404(new_owner_id) else: - update_old = UserUpdate(is_owner=False) - update_new = UserUpdate(is_owner=True) - new_client = ClientUpdate(**dict(client), owner_id=new_owner_id) + update_old = EmployeeUpdate(is_owner=False) + update_new = EmployeeUpdate(is_owner=True) old_owner = await pre_update_user(UUID4(client.owner_id), update_old) new_owner = await pre_update_user(new_owner_id, update_new) client = await update_client(client_id, new_client) @@ -201,7 +191,8 @@ async def add_owner(client_id: int, owner: OwnerCreate): detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, ) - message = f"Добро пожаловать в UDV Service Desk!\n\nВаш логин в системе: {owner.email}\nВаш пароль: {owner.password}" + message = f"Добро пожаловать в UDV Service Desk!\n\n" \ + f"Ваш логин в системе: {owner.email}\nВаш пароль: {owner.password}" await send_mail(owner.email, "Вы зарегистрированы в системе", message) updated_owner = await update_client_owner(client_id, created_owner.id) return updated_owner diff --git a/src/accounts/developer_account/routers.py b/src/accounts/developer_account/routers.py index 5090d13..3072cb9 100644 --- a/src/accounts/developer_account/routers.py +++ b/src/accounts/developer_account/routers.py @@ -1,12 +1,12 @@ from typing import List -from fastapi import APIRouter, Depends, status, Response +from fastapi import APIRouter, Depends, status, Response, HTTPException from pydantic.types import UUID4 from .services import get_developer, add_developer, delete_developer from src.users.models import UserTable -from src.users.logic import developer_user, get_developers, pre_update_user, change_pwd -from src.users.schemas import UserDB, UserCreate, UserUpdate, DeveloperList +from src.users.logic import developer_user, get_developers, change_pwd, pre_update_developer +from src.users.schemas import UserDB, UserUpdate, DeveloperList, DeveloperCreate developer_router = APIRouter() @@ -18,22 +18,25 @@ async def developers_list(user: UserTable = Depends(developer_user)): @developer_router.get("/{id:uuid}", response_model=UserDB, status_code=status.HTTP_200_OK) async def developer(id: UUID4, user: UserTable = Depends(developer_user)): - return await get_developer(id) + return await get_developer(str(id)) @developer_router.post("/") -async def create_developer(item: UserCreate, user: UserTable = Depends(developer_user)): +async def create_developer(item: DeveloperCreate, user: UserTable = Depends(developer_user)): return await add_developer(item) @developer_router.patch("/{id:uuid}", response_model=UserDB, status_code=status.HTTP_201_CREATED) async def update_developer_by_id(id: UUID4, item: UserUpdate, user: UserTable = Depends(developer_user)): - return await pre_update_user(id, item) + return await pre_update_developer(id, item) @developer_router.patch("/{id:uuid}/pwd", response_model=UserDB, status_code=status.HTTP_201_CREATED) async def change_dev_pwd(id: UUID4, new_pwd: str, user: UserTable = Depends(developer_user)): - return await change_pwd(id, new_pwd) + if user.id == id: + return await change_pwd(id, new_pwd) + else: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) @developer_router.delete("/{id:uuid}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) diff --git a/src/accounts/developer_account/services.py b/src/accounts/developer_account/services.py index 70a8d18..20b5451 100644 --- a/src/accounts/developer_account/services.py +++ b/src/accounts/developer_account/services.py @@ -11,16 +11,16 @@ from src.users.schemas import UserCreate, DeveloperCreate, UserUpdate, UserDB -async def get_developer(id: UUID4) -> Optional[UserDB]: - query = users.select().where((users.c.is_superuser is True) & (users.c.id == id)) +async def get_developer(developer_id: str) -> Optional[UserDB]: + query = users.select().where((users.c.is_superuser == 1) & (users.c.id == developer_id)) developer = await database.fetch_one(query=query) if developer: return UserDB(**dict(developer)) return None -async def add_developer(user: UserCreate) -> UserDB: - developer = DeveloperCreate(**user.dict()) +async def add_developer(developer: DeveloperCreate) -> UserDB: + developer = DeveloperCreate(**dict({**dict(developer), "is_superuser": True})) try: created_developer = await all_users.create_user(developer, safe=False) except Exception: @@ -29,8 +29,9 @@ async def add_developer(user: UserCreate) -> UserDB: status_code=status.HTTP_400_BAD_REQUEST, detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, ) - message = f"Добро пожаловать в UDV Service Desk!\n\nВаш логин в системе: {user.email}\nВаш пароль: {user.password}" - await send_mail(user.email, "Вы зарегистрированы в системе", message) + message = f"Добро пожаловать в UDV Service Desk!\n\nВаш логин в системе: " \ + f"{developer.email}\nВаш пароль: {developer.password}" + await send_mail(developer.email, "Вы зарегистрированы в системе", message) return created_developer diff --git a/src/errors.py b/src/errors.py index ca24213..5511593 100644 --- a/src/errors.py +++ b/src/errors.py @@ -22,3 +22,4 @@ class Errors: MODULE_FOR_THIS_SOFTWARE_IS_NOT_EXIST = "MODULE_FOR_THIS_SOFTWARE_IS_NOT_EXIST" CLIENT_NOT_FOUND = "CLIENT_NOT_FOUND" + FORBIDDEN_CHANGE_EMAIL = "FORBIDDEN_CHANGE_EMAIL" diff --git a/src/users/logic.py b/src/users/logic.py index 40f664d..eabe166 100644 --- a/src/users/logic.py +++ b/src/users/logic.py @@ -79,19 +79,20 @@ async def after_verification(user: UserDB, request: Request): print(f"{user.id} is now verified.") -async def get_count_dev_appeals(developer_id: UUID4): +async def get_count_dev_appeals(developer_id: str) -> int: query = appeals.select().where(appeals.c.responsible_id == developer_id) result = await database.fetch_all(query=query) return len(result) # TODO проверить не будет ли ошибки на len(None) async def get_developers() -> List[DeveloperList]: - query = users.select().where(users.c.is_superuser is True) + query = users.select().where(users.c.is_superuser == 1) result = await database.fetch_all(query=query) developers = [] for developer in result: developer = dict(developer) - count_appeals = await get_count_dev_appeals(developer["id"]) + print(developer) + count_appeals = await get_count_dev_appeals(str(developer["id"])) developers.append(DeveloperList(**dict({**developer, "count_appeals": count_appeals}))) return developers @@ -115,18 +116,29 @@ async def pre_update_user(id: UUID4, item: EmployeeUpdate) -> UserDB: licence = await update_employee_licence(id, item.licence_id) update_employee = UserUpdate(**dict(item)) update_dict = update_employee.dict(exclude_unset=True) + updated_user = await update_user(id, update_dict) + return updated_user + + +async def pre_update_developer(id: UUID4, item: UserUpdate) -> UserDB: + update_dict = item.dict(exclude_unset=True) + if "email" in update_dict.keys(): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=Errors.FORBIDDEN_CHANGE_EMAIL + ) updated_developer = await update_user(id, update_dict) return updated_developer async def update_user(id: UUID4, update_dict: Dict[str, Any]) -> UserDB: user = await get_or_404(id) - user = {**user} + user = dict(user) for field in update_dict: if field == "password": hashed_password = get_password_hash(update_dict[field]) user["hashed_password"] = hashed_password - else: + elif update_dict[field]: user[field] = update_dict[field] updated_user = await user_db.update(UserDB(**user)) @@ -179,7 +191,12 @@ async def change_pwd(id: UUID4, pwd: str) -> UserDB: if len(pwd) < 6: raise ValueError('Password should be at least 6 characters') updated_user = EmployeeUpdate(**dict({"password": pwd})) - return await pre_update_user(id, updated_user) + updated_user = await pre_update_user(id, updated_user) + + message = f"Добрый день!\nВы изменили свой пароль на: {pwd}\n" \ + f"Если Вы не меняли пароль обратитесь к администратору или смените его самостоятельно" + await send_mail(updated_user.email, "Вы зарегистрированы в системе", message) + return updated_user async def get_new_password(email: EmailStr) -> UserDB: diff --git a/src/users/schemas.py b/src/users/schemas.py index fb79285..73e0678 100644 --- a/src/users/schemas.py +++ b/src/users/schemas.py @@ -56,6 +56,7 @@ class PreEmployeeCreate(EmployeeCreate, models.BaseUserCreate): class DeveloperCreate(UserCreate, models.BaseUserCreate): + is_superuser = True date_reg: datetime = datetime.utcnow() From c2b858640dd703a799ab1ea62102d220a2f9aabe Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Mon, 31 May 2021 22:25:30 +0500 Subject: [PATCH 45/55] update func login --- src/users/routes.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/users/routes.py b/src/users/routes.py index 440a43d..9dde0be 100644 --- a/src/users/routes.py +++ b/src/users/routes.py @@ -1,7 +1,10 @@ -from fastapi import Depends, Response +from fastapi import Depends, Response, HTTPException from fastapi import APIRouter, status +from fastapi_users.router import ErrorCode +from fastapi.security import OAuth2PasswordRequestForm from pydantic import EmailStr +from .models import user_db from .schemas import UserDB from ..config import SECRET @@ -34,10 +37,25 @@ async def forgot_password(email: EmailStr): return get_new_password(email) -router.include_router( - all_users.get_auth_router(jwt_authentication), - prefix="/auth/jwt", - tags=["auth"]) +@router.post("/login") +async def login( + response: Response, credentials: OAuth2PasswordRequestForm = Depends() +): + user = await user_db.authenticate(credentials) + + if user is None or not user.is_active: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ErrorCode.LOGIN_BAD_CREDENTIALS, + ) + token = await jwt_authentication.get_login_response(user, response) + return {"token": token, "user": user} + + +# router.include_router( +# all_users.get_auth_router(jwt_authentication), +# prefix="/auth/jwt", +# tags=["auth"]) router.include_router( all_users.get_register_router(), prefix="/auth", From 30e1729c2a6853d96247c166489a568931e873cf Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Tue, 1 Jun 2021 00:29:57 +0500 Subject: [PATCH 46/55] =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BE=D1=81=D0=BF?= =?UTF-8?q?=D0=BE=D1=81=D0=BE=D0=B1=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=BB?= =?UTF-8?q?=D0=BA=20=D0=BF=D1=80=D0=B5=D0=B4=D1=81=D1=82=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=B8=20=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B4=D1=80=D1=83=D0=B3=D0=B8=D0=B5?= =?UTF-8?q?=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/client_account/schemas.py | 2 +- src/accounts/client_account/services.py | 6 ++--- src/accounts/employee_account/routers.py | 16 ++++++------- src/accounts/employee_account/services.py | 12 ++++++---- src/app.py | 2 +- src/reference_book/services.py | 6 ++--- src/users/logic.py | 28 ++++++++++++----------- src/users/{routes.py => routers.py} | 18 +++++++-------- src/users/schemas.py | 3 ++- 9 files changed, 49 insertions(+), 44 deletions(-) rename src/users/{routes.py => routers.py} (87%) diff --git a/src/accounts/client_account/schemas.py b/src/accounts/client_account/schemas.py index fc81d70..f6e4c7e 100644 --- a/src/accounts/client_account/schemas.py +++ b/src/accounts/client_account/schemas.py @@ -85,7 +85,7 @@ class ClientsPage(BaseModel): class EmployeePage(BaseModel): - employees: Employee + employee: Employee client: Client licences: List[Licence] diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index 75d6b98..5ab80f2 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -14,7 +14,7 @@ from ...reference_book.schemas import LicenceDB from ...reference_book.services import add_client_licence, get_client_licences, get_software_db_list, \ add_employee_licence, get_employee_licence, get_licences_db -from ...users.logic import all_users, get_or_404, pre_update_user +from ...users.logic import all_users, get_or_404, pre_update_user, user_is_active from ...users.models import users from ...users.schemas import UserCreate, OwnerCreate, Employee, UserDB, EmployeeList, EmployeeUpdate from ...service import send_mail @@ -69,7 +69,7 @@ async def get_client_page(client_id: int) -> ClientPage: client = await get_client(client_id) employees_list = await get_employees(client_id) licences_list = await get_client_licences(client_id) - return ClientPage(**dict({**client, + return ClientPage(**dict({"client": client, "employees_list": employees_list, "licences_list": licences_list})) @@ -166,7 +166,7 @@ async def get_client_owner(client_id: int) -> Employee: async def update_client_owner(client_id: int, new_owner_id: UUID4) -> Optional[UserDB]: client = await get_client_db(client_id) - if client: + if client and await user_is_active(new_owner_id): new_client = ClientUpdate(owner_id=str(new_owner_id)) if client.owner_id == "undefined": client = await update_client(client_id, new_client) diff --git a/src/accounts/employee_account/routers.py b/src/accounts/employee_account/routers.py index 8ad77f9..f805899 100644 --- a/src/accounts/employee_account/routers.py +++ b/src/accounts/employee_account/routers.py @@ -44,24 +44,22 @@ async def update_employee_by_id(id: int, pk: UUID4, item: EmployeeUpdate, user: return await pre_update_user(pk, item) -@employee_router.post("/{id}/employees/{pk}/make_owner", response_model=UserDB, status_code=status.HTTP_201_CREATED) -async def block_employee_by_id(id: int, pk: UUID4, user: UserTable = Depends(any_user)): - # TODO отправляет post or patch? +@employee_router.patch("/{id}/employees/{pk}/make_owner", response_model=UserDB, status_code=status.HTTP_201_CREATED) +async def make_employee_owner(id: int, pk: UUID4, user: UserTable = Depends(any_user)): user = await get_owner(id, user) return await update_client_owner(id, pk) -@employee_router.post("/{id}/employees/{pk}/block", response_model=UserDB, status_code=status.HTTP_201_CREATED) +@employee_router.patch("/{id}/employees/{pk}/block", response_model=UserDB, status_code=status.HTTP_201_CREATED) async def block_employee_by_id(id: int, pk: UUID4, user: UserTable = Depends(any_user)): - # TODO отправляет post or patch? user = await get_owner(id, user) return await block_employee(pk) -@employee_router.patch("/{id:uuid}/pwd", response_model=UserDB, status_code=status.HTTP_201_CREATED) -async def change_employee_pwd(id: UUID4, new_pwd: str, user: UserTable = Depends(any_user)): - if user.id == id: - return await change_pwd(id, new_pwd) +@employee_router.patch("/{id}/employees/{pk}/pwd", response_model=UserDB, status_code=status.HTTP_201_CREATED) +async def change_employee_pwd(id: int, pk: UUID4, new_pwd: str, user: UserTable = Depends(any_user)): + if str(user.id) == str(pk): + return await change_pwd(pk, new_pwd) else: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) diff --git a/src/accounts/employee_account/services.py b/src/accounts/employee_account/services.py index 16e3983..d553edf 100644 --- a/src/accounts/employee_account/services.py +++ b/src/accounts/employee_account/services.py @@ -9,9 +9,9 @@ from src.db.db import database from src.reference_book.services import get_client_licences, add_employee_licence from src.service import send_mail -from src.users.logic import all_users, update_user, delete_user +from src.users.logic import all_users, update_user, delete_user, get_or_404 from src.users.models import users -from src.users.schemas import EmployeeCreate, PreEmployeeCreate, UserDB +from src.users.schemas import EmployeeCreate, PreEmployeeCreate, UserDB, UserUpdate async def get_employee(client_id: int, pk: UUID4) -> Optional[EmployeePage]: @@ -20,7 +20,7 @@ async def get_employee(client_id: int, pk: UUID4) -> Optional[EmployeePage]: employee = dict(employee) client = await get_client(client_id) client_licences = client.licences - return EmployeePage(**dict({**employee, "client": client, "licences": client_licences})) + return EmployeePage(**dict({"employee": employee, "client": client, "licences": client_licences})) return None @@ -37,6 +37,7 @@ async def add_employee(id: int, user: PreEmployeeCreate) -> UserDB: user.client_id = id user.is_owner = False employee = EmployeeCreate(**user.dict()) + print(employee) try: created_user = await all_users.create_user(employee, safe=True) except Exception: @@ -57,6 +58,9 @@ async def delete_employee(pk: UUID4): async def block_employee(pk: UUID4): - update_dict = {"is_active": False} + item = await get_or_404(pk) + update_employee = UserUpdate(**dict({**dict(item), "is_active": False})) + update_dict = update_employee.dict(exclude_unset=True, + exclude={"id", "email", "is_superuser", "is_verified"}) updated_employee = await update_user(pk, update_dict) return updated_employee diff --git a/src/app.py b/src/app.py index 59915c8..6c49219 100644 --- a/src/app.py +++ b/src/app.py @@ -7,7 +7,7 @@ from .desk.routes import router as desk_router from .reference_book.api.routes import router as book_router from .users.logic import create_developer -from .users.routes import router as users_routes +from .users.routers import router as users_routes Base.metadata.create_all(bind=engine) app = FastAPI() diff --git a/src/reference_book/services.py b/src/reference_book/services.py index 1e10196..a01deb6 100644 --- a/src/reference_book/services.py +++ b/src/reference_book/services.py @@ -318,7 +318,7 @@ async def get_employee_licence(employee_id: str) -> Optional[LicenceDB]: async def add_employee_licence(employee_id: str, licence_id: int) -> EmployeeLicenceDB: - if await get_employee_licence(employee_id): + if await get_employee_licence(str(employee_id)): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=Errors.USER_HAS_ANOTHER_LICENCE, @@ -333,13 +333,13 @@ async def add_employee_licence(employee_id: str, licence_id: int) -> EmployeeLic status_code=status.HTTP_400_BAD_REQUEST, detail=Errors.LICENCE_IS_FULL, ) - employee_licence = EmployeeLicenceCreate(**dict({"employee_id": employee_id, "licence_id": licence_id})) + employee_licence = EmployeeLicenceCreate(**dict({"employee_id": str(employee_id), "licence_id": licence_id})) query = employee_licences.insert().values(**employee_licence.dict()) employee_licence_id = await database.execute(query) return EmployeeLicenceDB(**dict({"id": employee_licence_id, **employee_licence.dict()})) -async def update_employee_licence(employee_id: UUID4, licence_id: int) -> EmployeeLicenceDB: +async def update_employee_licence(employee_id: str, licence_id: int) -> EmployeeLicenceDB: if await get_free_vacancy_in_licence(licence_id) <= 0: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/src/users/logic.py b/src/users/logic.py index eabe166..af5b161 100644 --- a/src/users/logic.py +++ b/src/users/logic.py @@ -17,7 +17,7 @@ from pydantic import EmailStr from ..desk.models import appeals from ..reference_book.services import update_employee_licence -from ..service import send_mail +from ..service import send_mail, Email auth_backends = [] @@ -63,6 +63,13 @@ async def get_client_users_with_superuser(client_id: int, user: UserTable = Depe return user +async def user_is_active(user_id: UUID4) -> bool: + user = await get_or_404(user_id) + if user.is_active: + return True + return False + + async def on_after_forgot_password(user: UserDB, token: str, request: Request): print(f"User {user.id} has forgot their password. Reset token: {token}") @@ -113,15 +120,16 @@ async def get_user(id: UUID4) -> Optional[UserDB]: async def pre_update_user(id: UUID4, item: EmployeeUpdate) -> UserDB: if item.licence_id: - licence = await update_employee_licence(id, item.licence_id) + licence = await update_employee_licence(str(id), item.licence_id) update_employee = UserUpdate(**dict(item)) - update_dict = update_employee.dict(exclude_unset=True) + update_dict = update_employee.dict(exclude_unset=True, + exclude={"id", "email", "is_superuser", "is_verified"}) updated_user = await update_user(id, update_dict) return updated_user async def pre_update_developer(id: UUID4, item: UserUpdate) -> UserDB: - update_dict = item.dict(exclude_unset=True) + update_dict = item.dict(exclude_unset=True, exclude={"id"}) if "email" in update_dict.keys(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -135,12 +143,11 @@ async def update_user(id: UUID4, update_dict: Dict[str, Any]) -> UserDB: user = await get_or_404(id) user = dict(user) for field in update_dict: - if field == "password": + if field == "password" and update_dict[field]: hashed_password = get_password_hash(update_dict[field]) user["hashed_password"] = hashed_password - elif update_dict[field]: + elif update_dict[field] is not None: user[field] = update_dict[field] - updated_user = await user_db.update(UserDB(**user)) return updated_user @@ -201,10 +208,5 @@ async def change_pwd(id: UUID4, pwd: str) -> UserDB: async def get_new_password(email: EmailStr) -> UserDB: user = await get_user_by_email(email) - pwd = await generate_pwd() - - message = f"Добрый день!\nВаш новый пароль: {pwd}\n" \ - f"Если Вы не меняли пароль обратитесь к администратору или смените его самостоятельно" - await send_mail(user.email, "Вы зарегистрированы в системе", message) - + pwd = generate_pwd() return await change_pwd(user.id, pwd) diff --git a/src/users/routes.py b/src/users/routers.py similarity index 87% rename from src/users/routes.py rename to src/users/routers.py index 9dde0be..07e68d2 100644 --- a/src/users/routes.py +++ b/src/users/routers.py @@ -34,10 +34,10 @@ async def developer(): @router.post("/forgot_password", response_model=UserDB, status_code=status.HTTP_201_CREATED) async def forgot_password(email: EmailStr): - return get_new_password(email) + return await get_new_password(email) -@router.post("/login") +@router.post("/auth/jwt/login") async def login( response: Response, credentials: OAuth2PasswordRequestForm = Depends() ): @@ -60,13 +60,13 @@ async def login( all_users.get_register_router(), prefix="/auth", tags=["auth"]) -router.include_router( - all_users.get_reset_password_router( - SECRET, - after_forgot_password=on_after_forgot_password, - after_reset_password=on_after_reset_password), - prefix="/auth", - tags=["auth"]) +# router.include_router( +# all_users.get_reset_password_router( +# SECRET, +# after_forgot_password=on_after_forgot_password, +# after_reset_password=on_after_reset_password), +# prefix="/auth", +# tags=["auth"]) # router.include_router( # all_users.get_users_router(), # prefix="/users", diff --git a/src/users/schemas.py b/src/users/schemas.py index 73e0678..2a7e6c1 100644 --- a/src/users/schemas.py +++ b/src/users/schemas.py @@ -53,6 +53,7 @@ class EmployeeCreate(UserCreate, models.BaseUserCreate): class PreEmployeeCreate(EmployeeCreate, models.BaseUserCreate): licence_id: int + client_id: Optional[int] class DeveloperCreate(UserCreate, models.BaseUserCreate): @@ -69,7 +70,7 @@ class OwnerCreate(UserCreate, models.BaseUserCreate): class UserUpdate(User, models.BaseUserUpdate): name: Optional[str] surname: Optional[str] - is_owner: Optional[bool] + is_owner: Optional[bool] = False is_active: Optional[bool] date_block: Optional[datetime] email: Optional[EmailStr] From e9a3f34f2cc1d8fb3ab941c8f47d902b9226df92 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Tue, 1 Jun 2021 13:51:34 +0500 Subject: [PATCH 47/55] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20=D1=8D=D0=BB=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D1=80=D0=BE=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D1=8C=D0=BC=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/client_account/services.py | 13 +++++-- src/accounts/developer_account/routers.py | 7 ++-- src/accounts/developer_account/services.py | 13 +++++-- src/accounts/employee_account/routers.py | 6 ++- src/accounts/employee_account/services.py | 12 ++++-- src/service.py | 18 ++++++--- src/users/logic.py | 44 +++++++++++++--------- 7 files changed, 74 insertions(+), 39 deletions(-) diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index 5ab80f2..a4f9780 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -17,7 +17,7 @@ from ...users.logic import all_users, get_or_404, pre_update_user, user_is_active from ...users.models import users from ...users.schemas import UserCreate, OwnerCreate, Employee, UserDB, EmployeeList, EmployeeUpdate -from ...service import send_mail +from ...service import send_mail, Email async def get_count_appeals(employee_id: str) -> int: @@ -191,8 +191,13 @@ async def add_owner(client_id: int, owner: OwnerCreate): detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, ) - message = f"Добро пожаловать в UDV Service Desk!\n\n" \ - f"Ваш логин в системе: {owner.email}\nВаш пароль: {owner.password}" - await send_mail(owner.email, "Вы зарегистрированы в системе", message) + await send_mail_with_owner_pwd(owner) updated_owner = await update_client_owner(client_id, created_owner.id) return updated_owner + + +async def send_mail_with_owner_pwd(user: OwnerCreate) -> None: + message = f"Добро пожаловать в UDV Service Desk!\n\n" \ + f"Ваш логин в системе: {user.email}\nВаш пароль: {user.password}" + email = Email(recipient=user.email, title="Регистрация в UDV Service Desk", message=message) + await send_mail(email) diff --git a/src/accounts/developer_account/routers.py b/src/accounts/developer_account/routers.py index 3072cb9..ad7bfba 100644 --- a/src/accounts/developer_account/routers.py +++ b/src/accounts/developer_account/routers.py @@ -5,7 +5,7 @@ from .services import get_developer, add_developer, delete_developer from src.users.models import UserTable -from src.users.logic import developer_user, get_developers, change_pwd, pre_update_developer +from src.users.logic import developer_user, get_developers, change_pwd, pre_update_developer, get_email_with_changed_pwd from src.users.schemas import UserDB, UserUpdate, DeveloperList, DeveloperCreate developer_router = APIRouter() @@ -33,8 +33,9 @@ async def update_developer_by_id(id: UUID4, item: UserUpdate, user: UserTable = @developer_router.patch("/{id:uuid}/pwd", response_model=UserDB, status_code=status.HTTP_201_CREATED) async def change_dev_pwd(id: UUID4, new_pwd: str, user: UserTable = Depends(developer_user)): - if user.id == id: - return await change_pwd(id, new_pwd) + if str(user.id) == str(id): + email = await get_email_with_changed_pwd(new_pwd, user) + return await change_pwd(id, new_pwd, email) else: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) diff --git a/src/accounts/developer_account/services.py b/src/accounts/developer_account/services.py index 20b5451..6228441 100644 --- a/src/accounts/developer_account/services.py +++ b/src/accounts/developer_account/services.py @@ -5,7 +5,7 @@ from pydantic.types import UUID4 from src.db.db import database -from src.service import send_mail +from src.service import send_mail, Email from src.users.logic import all_users, delete_user, pre_update_user from src.users.models import users from src.users.schemas import UserCreate, DeveloperCreate, UserUpdate, UserDB @@ -29,11 +29,16 @@ async def add_developer(developer: DeveloperCreate) -> UserDB: status_code=status.HTTP_400_BAD_REQUEST, detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS, ) - message = f"Добро пожаловать в UDV Service Desk!\n\nВаш логин в системе: " \ - f"{developer.email}\nВаш пароль: {developer.password}" - await send_mail(developer.email, "Вы зарегистрированы в системе", message) + await send_mail_with_dev_pwd(developer) return created_developer +async def send_mail_with_dev_pwd(user: DeveloperCreate) -> None: + message = f"Добро пожаловать в UDV Service Desk!\n\n" \ + f"Ваш логин в системе: {user.email}\nВаш пароль: {user.password}" + email = Email(recipient=user.email, title="Регистрация в UDV Service Desk", message=message) + await send_mail(email) + + async def delete_developer(id: UUID4): await delete_user(id) diff --git a/src/accounts/employee_account/routers.py b/src/accounts/employee_account/routers.py index f805899..5a3d968 100644 --- a/src/accounts/employee_account/routers.py +++ b/src/accounts/employee_account/routers.py @@ -5,7 +5,8 @@ from .services import add_employee, get_count_allowed_employees, get_employee, delete_employee, block_employee from src.errors import Errors -from src.users.logic import get_owner, any_user, get_client_users_with_superuser, pre_update_user, change_pwd +from src.users.logic import get_owner, any_user, get_client_users_with_superuser, pre_update_user, change_pwd, \ + get_email_with_changed_pwd from src.users.models import UserTable from src.users.schemas import UserDB, PreEmployeeCreate, EmployeeUpdate from ..client_account.schemas import EmployeePage @@ -59,7 +60,8 @@ async def block_employee_by_id(id: int, pk: UUID4, user: UserTable = Depends(any @employee_router.patch("/{id}/employees/{pk}/pwd", response_model=UserDB, status_code=status.HTTP_201_CREATED) async def change_employee_pwd(id: int, pk: UUID4, new_pwd: str, user: UserTable = Depends(any_user)): if str(user.id) == str(pk): - return await change_pwd(pk, new_pwd) + email = await get_email_with_changed_pwd(new_pwd, user) + return await change_pwd(pk, new_pwd, email) else: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) diff --git a/src/accounts/employee_account/services.py b/src/accounts/employee_account/services.py index d553edf..e90d563 100644 --- a/src/accounts/employee_account/services.py +++ b/src/accounts/employee_account/services.py @@ -8,7 +8,7 @@ from src.accounts.client_account.services import get_client, get_employees from src.db.db import database from src.reference_book.services import get_client_licences, add_employee_licence -from src.service import send_mail +from src.service import send_mail, Email from src.users.logic import all_users, update_user, delete_user, get_or_404 from src.users.models import users from src.users.schemas import EmployeeCreate, PreEmployeeCreate, UserDB, UserUpdate @@ -48,11 +48,17 @@ async def add_employee(id: int, user: PreEmployeeCreate) -> UserDB: licence_id = user.licence_id licence = await add_employee_licence(created_user.id, licence_id) - message = f"Добро пожаловать в UDV Service Desk!\n\nВаш логин в системе: {user.email}\nВаш пароль: {user.password}" - await send_mail(user.email, "Вы зарегистрированы в системе", message) + await send_mail_with_pwd(user) return created_user +async def send_mail_with_pwd(user: PreEmployeeCreate) -> None: + message = f"Добро пожаловать в UDV Service Desk!\n\n" \ + f"Ваш логин в системе: {user.email}\nВаш пароль: {user.password}" + email = Email(recipient=user.email, title="Регистрация в UDV Service Desk", message=message) + await send_mail(email) + + async def delete_employee(pk: UUID4): await delete_user(pk) diff --git a/src/service.py b/src/service.py index b94db5c..9d76ea3 100644 --- a/src/service.py +++ b/src/service.py @@ -3,6 +3,8 @@ from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart +from pydantic import BaseModel + from src.config import MAIL_LOGIN, MAIL_PWD @@ -12,16 +14,22 @@ async def check_dict(result): return None -async def send_mail(to_addr: str, subject: str, msg: str): +class Email(BaseModel): + recipient: str + title: str + message: str + + +async def send_mail(email: Email): multipart_msg = MIMEMultipart() multipart_msg['From'] = MAIL_LOGIN - multipart_msg['To'] = to_addr - multipart_msg['Subject'] = subject - multipart_msg.attach(MIMEText(msg, 'plain')) + multipart_msg['To'] = email.recipient + multipart_msg['Subject'] = email.title + multipart_msg.attach(MIMEText(email.message, 'plain')) smtp_obj = smtplib.SMTP('smtp.gmail.com', 587) smtp_obj.starttls() smtp_obj.login(MAIL_LOGIN, MAIL_PWD) smtp_obj.send_message(multipart_msg) smtp_obj.quit() - print(f"письмо отправлено {to_addr}") + print(f"письмо отправлено {email.recipient}") diff --git a/src/users/logic.py b/src/users/logic.py index af5b161..d9123c8 100644 --- a/src/users/logic.py +++ b/src/users/logic.py @@ -104,8 +104,8 @@ async def get_developers() -> List[DeveloperList]: return developers -async def get_or_404(id: UUID4) -> UserDB: - user = await database.fetch_one(query=users.select().where(users.c.id == id)) +async def get_or_404(user_id: UUID4) -> UserDB: + user = await database.fetch_one(query=users.select().where(users.c.id == user_id)) if user is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -113,34 +113,34 @@ async def get_or_404(id: UUID4) -> UserDB: return user -async def get_user(id: UUID4) -> Optional[UserDB]: - user = await database.fetch_one(query=users.select().where(users.c.id == id)) +async def get_user(user_id: UUID4) -> Optional[UserDB]: + user = await database.fetch_one(query=users.select().where(users.c.id == user_id)) return user -async def pre_update_user(id: UUID4, item: EmployeeUpdate) -> UserDB: +async def pre_update_user(user_id: UUID4, item: EmployeeUpdate) -> UserDB: if item.licence_id: - licence = await update_employee_licence(str(id), item.licence_id) + licence = await update_employee_licence(str(user_id), item.licence_id) update_employee = UserUpdate(**dict(item)) update_dict = update_employee.dict(exclude_unset=True, exclude={"id", "email", "is_superuser", "is_verified"}) - updated_user = await update_user(id, update_dict) + updated_user = await update_user(user_id, update_dict) return updated_user -async def pre_update_developer(id: UUID4, item: UserUpdate) -> UserDB: +async def pre_update_developer(user_id: UUID4, item: UserUpdate) -> UserDB: update_dict = item.dict(exclude_unset=True, exclude={"id"}) if "email" in update_dict.keys(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=Errors.FORBIDDEN_CHANGE_EMAIL ) - updated_developer = await update_user(id, update_dict) + updated_developer = await update_user(user_id, update_dict) return updated_developer -async def update_user(id: UUID4, update_dict: Dict[str, Any]) -> UserDB: - user = await get_or_404(id) +async def update_user(user_id: UUID4, update_dict: Dict[str, Any]) -> UserDB: + user = await get_or_404(user_id) user = dict(user) for field in update_dict: if field == "password" and update_dict[field]: @@ -153,8 +153,8 @@ async def update_user(id: UUID4, update_dict: Dict[str, Any]) -> UserDB: return updated_user -async def delete_user(id: UUID4): - user = await get_or_404(id) +async def delete_user(user_id: UUID4): + user = await get_or_404(user_id) await user_db.delete(user) return None @@ -194,19 +194,27 @@ async def get_user_by_email(email: EmailStr) -> UserDB: return user -async def change_pwd(id: UUID4, pwd: str) -> UserDB: +async def change_pwd(user_id: UUID4, pwd: str, email: Email) -> UserDB: if len(pwd) < 6: raise ValueError('Password should be at least 6 characters') updated_user = EmployeeUpdate(**dict({"password": pwd})) - updated_user = await pre_update_user(id, updated_user) + updated_user = await pre_update_user(user_id, updated_user) + + await send_mail(email) + return updated_user + +async def get_email_with_changed_pwd(pwd: str, user: UserTable) -> Email: message = f"Добрый день!\nВы изменили свой пароль на: {pwd}\n" \ f"Если Вы не меняли пароль обратитесь к администратору или смените его самостоятельно" - await send_mail(updated_user.email, "Вы зарегистрированы в системе", message) - return updated_user + email = Email(recipient=user.email, title="Смена пароля в UDV Service Desk", message=message) + return email async def get_new_password(email: EmailStr) -> UserDB: user = await get_user_by_email(email) pwd = generate_pwd() - return await change_pwd(user.id, pwd) + message = f"Добрый день!\nВаш новый пароль: {pwd}\n" \ + f"Если Вы не запрашивали смену пароля обратитесь к администратору или смените его самостоятельно" + email = Email(recipient=user.email, title="Смена пароля в UDV Service Desk", message=message) + return await change_pwd(user.id, pwd, email) From b95b54500f7d3a687cd9ff4ff15b9f5a092628e6 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Tue, 1 Jun 2021 13:52:03 +0500 Subject: [PATCH 48/55] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B2=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BA=20=D0=BE=D0=B1=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/desk/models.py | 12 ++- src/desk/routes.py | 45 ++++++++--- src/desk/schemas.py | 17 +++++ src/desk/services.py | 173 ++++++++++++++++++++++++++++++++++++------- 4 files changed, 211 insertions(+), 36 deletions(-) diff --git a/src/desk/models.py b/src/desk/models.py index 7d9d0e3..e47f3ed 100644 --- a/src/desk/models.py +++ b/src/desk/models.py @@ -30,7 +30,6 @@ class Appeal(Base): software_id = Column(Integer, ForeignKey('software.id'), nullable=False) module_id = Column(Integer, ForeignKey('module.id'), nullable=False) importance = Column(Integer, default=1) - # attachments class Comment(Base): @@ -43,5 +42,16 @@ class Comment(Base): date_create = Column(DateTime(timezone=True), server_default=sql.func.now()) +class Attachment(Base): + __tablename__ = 'attachment' + + id = Column(Integer, primary_key=True, index=True, unique=True) + filename = Column(String(300), nullable=False) + appeal_id = Column(Integer, ForeignKey('appeal.id'), nullable=False) + author_id = Column(String, ForeignKey('user.id'), nullable=False) + date_create = Column(DateTime(timezone=True), server_default=sql.func.now()) + + appeals = Appeal.__table__ comments = Comment.__table__ +attachments = Attachment.__table__ diff --git a/src/desk/routes.py b/src/desk/routes.py index c45d11c..d4a6f75 100644 --- a/src/desk/routes.py +++ b/src/desk/routes.py @@ -1,12 +1,13 @@ -from fastapi import APIRouter, status, Depends, Response, HTTPException +from fastapi import APIRouter, status, Depends, Response, HTTPException, UploadFile from typing import List, Union from .services import get_all_appeals, get_appeal, get_comments, get_comment, \ - add_appeal, add_comment, update_appeal, update_comment, delete_comment, get_appeals_page + add_appeal, add_comment, update_appeal, update_comment, delete_comment, get_appeals_page, upload_attachment, \ + delete_attachment, get_attachment, update_dev_appeal, check_access from .schemas import Appeal, CommentShort, Comment, AppealCreate, CommentCreate, CommentDB, \ - AppealUpdate, AppealDB, AppealShort, CommentUpdate, DevAppeal + AppealUpdate, AppealDB, AppealShort, CommentUpdate, DevAppeal, AttachmentDB from ..users.models import UserTable -from ..users.logic import employee, any_user, developer_user +from ..users.logic import employee, any_user router = APIRouter() @@ -19,8 +20,9 @@ async def appeals_list(user: UserTable = Depends(any_user)): @router.get("/{id}", response_model=Union[Appeal, DevAppeal], status_code=status.HTTP_200_OK) -async def appeal(appeal_id: int, user: UserTable = Depends(any_user)): - return await get_appeal(appeal_id, user) +async def appeal(id: int, user: UserTable = Depends(any_user)): + await check_access(id, user, status.HTTP_403_FORBIDDEN) + return await get_appeal(id, user) @router.post("/", response_model=AppealDB, status_code=status.HTTP_201_CREATED) @@ -30,32 +32,57 @@ async def create_appeal(item: AppealCreate, user: UserTable = Depends(employee)) return await add_appeal(item, user) -@router.patch("/{id}", response_model=AppealDB, status_code=status.HTTP_201_CREATED) # TODO сделать изменение обращения пользователем -async def update_appeal_by_id(appeal_id: int, item: AppealUpdate, user: UserTable = Depends(developer_user)): +@router.patch("/{id}", response_model=AppealDB, status_code=status.HTTP_201_CREATED) +async def update_appeal_by_id(appeal_id: int, item: AppealUpdate, user: UserTable = Depends(any_user)): + if user.is_superuser: + return await update_dev_appeal(appeal_id, item, user) return await update_appeal(appeal_id, item, user) @router.get("/{id}/comments", response_model=List[CommentShort], status_code=status.HTTP_200_OK) async def comments_list(id: int, user: UserTable = Depends(any_user)): + await check_access(id, user, status.HTTP_403_FORBIDDEN) return await get_comments(id, user) @router.get("/{id}/comments/{pk}", response_model=Comment, status_code=status.HTTP_200_OK) async def comment(id: int, pk: int, user: UserTable = Depends(any_user)): + await check_access(id, user, status.HTTP_403_FORBIDDEN) return await get_comment(id, pk, user) @router.post("/{id}/comments/", response_model=CommentDB, status_code=status.HTTP_201_CREATED) async def create_comment(id: int, item: CommentCreate, user: UserTable = Depends(any_user)): + await check_access(id, user, status.HTTP_403_FORBIDDEN) return await add_comment(id, item, user) @router.patch("/{id}/comments/{pk}", response_model=CommentDB, status_code=status.HTTP_201_CREATED) async def update_comment_by_id(id: int, pk: int, item: CommentUpdate, user: UserTable = Depends(any_user)): + await check_access(id, user, status.HTTP_403_FORBIDDEN) return await update_comment(id, pk, item, user) @router.delete("/{id}/comments/{pk}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) -async def delete_comment_by_id(id: int, pk: int, user: UserTable = Depends(employee)): +async def delete_comment_by_id(id: int, pk: int, user: UserTable = Depends(any_user)): + await check_access(id, user, status.HTTP_403_FORBIDDEN) await delete_comment(id, pk, user) + +@router.get("/{id}/attachments/{pk}", status_code=status.HTTP_200_OK) +async def get_attachment_by_id(id: int, pk: int, user: UserTable = Depends(any_user)): + await check_access(id, user, status.HTTP_403_FORBIDDEN) + return await get_attachment(pk) + + +@router.post("/{id}/attachments/", response_model=AttachmentDB, status_code=status.HTTP_201_CREATED) +async def upload_attachments(id: int, file: UploadFile, user: UserTable = Depends(any_user)): + await check_access(id, user, status.HTTP_403_FORBIDDEN) + return await upload_attachment(id, file, user) + + +@router.delete("/{id}/comments/{pk}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) +async def delete_attachment_by_id(id: int, pk: int, user: UserTable = Depends(any_user)): + await check_access(id, user, status.HTTP_403_FORBIDDEN) + await delete_attachment(pk) + diff --git a/src/desk/schemas.py b/src/desk/schemas.py index 6ea1fd9..fca1afa 100644 --- a/src/desk/schemas.py +++ b/src/desk/schemas.py @@ -41,6 +41,23 @@ class CommentShort(CommentBase): date_create: datetime +class AttachmentBase(BaseModel): + filename: str + + +class AttachmentCreate(AttachmentBase): + appeal_id: int + author_id: str + date_create: datetime = datetime.utcnow() + + +class AttachmentDB(AttachmentBase): + id: int + appeal_id: int + author_id: str + date_create: datetime + + class AppealBase(BaseModel): topic: str importance: int diff --git a/src/desk/services.py b/src/desk/services.py index c695101..7b520f0 100644 --- a/src/desk/services.py +++ b/src/desk/services.py @@ -1,23 +1,25 @@ +import shutil from datetime import datetime -from typing import List, Union +from typing import List, Union, Optional from pydantic.types import UUID4 from .schemas import AppealCreate, CommentCreate, AppealUpdate, CommentUpdate, AppealList, AppealDB, Appeal, DevAppeal, \ - AppealsPage + AppealsPage, AttachmentDB, AttachmentCreate from ..accounts.client_account.models import clients from ..accounts.client_account.services import get_client_db, get_client from ..db.db import database -from .models import appeals, comments +from .models import appeals, comments, attachments from ..errors import Errors from ..reference_book.models import softwares, modules from ..reference_book.services import get_software, get_module, get_modules, get_software_list, get_software_db_list -from ..users.logic import get_developers, get_or_404, get_user +from ..users.logic import get_developers, get_or_404, get_user, get_client_users from ..users.models import UserTable, users from .models import StatusTasks -from ..service import check_dict +from ..service import check_dict, send_mail, Email -from fastapi import HTTPException, status +from fastapi import HTTPException, status, UploadFile +from fastapi.responses import FileResponse async def get_all_appeals() -> List[AppealList]: @@ -47,7 +49,7 @@ async def get_appeals(user: UserTable) -> List[Appeal]: for appeal in result: appeal = dict(appeal) correct_appeal = await get_appeal(appeal["id"], user) - appeals_list.append(Appeal(correct_appeal)) + appeals_list.append(Appeal(**dict(correct_appeal))) return appeals_list @@ -93,8 +95,8 @@ async def get_appeal(appeal_id: int, user: UserTable) -> Union[Appeal, DevAppeal return result -async def get_appeal_db(id: int) -> AppealDB: - query = appeals.select().where(appeals.c.id == id) +async def get_appeal_db(appeal_id: int) -> AppealDB: + query = appeals.select().where(appeals.c.id == appeal_id) result = await database.fetch_one(query=query) if result: return AppealDB(**dict(result)) @@ -106,15 +108,31 @@ async def add_appeal(appeal: AppealCreate, user: UserTable) -> AppealDB: item = {**appeal.dict(), "client_id": int(user.client_id), "author_id": str(user.id), "status": StatusTasks.new} query = appeals.insert().values(item) appeal_id = await database.execute(query) + await notify_create_appeal(appeal_id, user) return await get_appeal_db(appeal_id) -async def update_attachments(id: int, appeal: AppealUpdate, user: UserTable): - # TODO сделать добавление вложений для пользователя - pass +async def update_appeal(appeal_id: int, appeal: AppealUpdate, user: UserTable) -> AppealDB: + old_appeal = await check_access(appeal_id, user, status.HTTP_403_FORBIDDEN) + if old_appeal.status == StatusTasks.closed or old_appeal.status == StatusTasks.canceled: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=Errors.APPEAL_IS_CLOSED) + appeal = appeal.dict(exclude_unset=True) + if "importance" in appeal: + if appeal["importance"] < 0: + appeal["importance"] = 1 + if appeal["importance"] > 5: + appeal["importance"] = 5 + old_appeal = dict(old_appeal) + for field in appeal: + if appeal[field] is not None: + old_appeal[field] = appeal[field] + query = appeals.update().where(appeals.c.id == appeal_id).values(old_appeal) + result_id = await database.execute(query) + await notify_update_appeal(appeal_id, user) + return await get_appeal_db(appeal_id) -async def update_appeal(appeal_id: int, appeal: AppealUpdate, user: UserTable) -> AppealDB: +async def update_dev_appeal(appeal_id: int, appeal: AppealUpdate, user: UserTable) -> AppealDB: old_appeal = await get_appeal_db(appeal_id) if old_appeal.status == StatusTasks.closed or old_appeal.status == StatusTasks.canceled: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=Errors.APPEAL_IS_CLOSED) @@ -130,28 +148,30 @@ async def update_appeal(appeal_id: int, appeal: AppealUpdate, user: UserTable) - appeal["importance"] = 5 old_appeal = dict(old_appeal) for field in appeal: - old_appeal[field] = appeal[field] + if appeal[field] is not None: + old_appeal[field] = appeal[field] query = appeals.update().where(appeals.c.id == appeal_id).values(old_appeal) result_id = await database.execute(query) + await notify_update_appeal(appeal_id, user) return await get_appeal_db(appeal_id) -async def delete_appeal(id: int): - query = appeals.delete().where(appeals.c.id == id) +async def delete_appeal(appeal_id: int): + query = appeals.delete().where(appeals.c.id == appeal_id) result = await database.execute(query) return result -async def get_comments(id: int, user: UserTable): - await check_access(id, user, status.HTTP_404_NOT_FOUND) - query = comments.select().where(comments.c.appeal_id == id) +async def get_comments(comment_id: int, user: UserTable): + await check_access(comment_id, user, status.HTTP_404_NOT_FOUND) + query = comments.select().where(comments.c.appeal_id == comment_id) result = await database.fetch_all(query) return [dict(comment) for comment in result] -async def get_comment(id: int, pk: int, user: UserTable): - await check_access(id, user, status.HTTP_404_NOT_FOUND) - query = comments.select().where((comments.c.id == pk) & (comments.c.appeal_id == id)) +async def get_comment(comment_id: int, pk: int, user: UserTable): + await check_access(comment_id, user, status.HTTP_404_NOT_FOUND) + query = comments.select().where((comments.c.id == pk) & (comments.c.appeal_id == comment_id)) comment = await database.fetch_one(query) if comment: comment = dict(comment) @@ -160,8 +180,8 @@ async def get_comment(id: int, pk: int, user: UserTable): return None -async def get_comment_db(id: int): - query = comments.select().where(comments.c.id == id) +async def get_comment_db(comment_id: int): + query = comments.select().where(comments.c.id == comment_id) result = await database.fetch_one(query=query) return await check_dict(result) @@ -171,6 +191,7 @@ async def add_comment(appeal_id: int, comment: CommentCreate, user: UserTable): item = {**comment.dict(), "appeal_id": int(appeal_id), "author_id": str(user.id)} query = comments.insert().values(item) comment_id = await database.execute(query) + await notify_comment(appeal_id, user) return await get_comment_db(comment_id) @@ -189,14 +210,14 @@ async def delete_comment(appeal_id: int, pk: int, user: UserTable): async def check_access(appeal_id: int, user: UserTable, status_code: status): appeal = await get_appeal_db(appeal_id) - if appeal["client_id"] != user.client_id and not user.is_superuser: + if appeal.client_id != user.client_id and not user.is_superuser: raise HTTPException(status_code=status_code) return appeal async def get_next_status(appeal_id: int, user: UserTable): appeal = await get_appeal_db(appeal_id) - current_status = appeal["status"] + current_status = appeal.status if user.is_superuser: if current_status is StatusTasks.new: return [StatusTasks.registered] @@ -208,3 +229,103 @@ async def get_next_status(appeal_id: int, user: UserTable): return [StatusTasks.in_work] else: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=Errors.USER_CAN_NOT_CHANGE_STATUS) + + +async def get_attachment_db(attachment_id: int) -> Optional[AttachmentDB]: + query = attachments.select().where(attachments.c.id == attachment_id) + result = await database.fetch_one(query=query) + if result: + return AttachmentDB(**dict(result)) + return None + + +async def get_attachment(attachment_id: int): + attachment = await get_attachment_db(attachment_id) + if attachment: + return FileResponse(attachment.filename) + else: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + + +async def add_attachment(appeal_id: int, filename: str, user_id: str) -> Optional[AttachmentDB]: + attachment = AttachmentCreate(filename=filename, + appeal_id=appeal_id, + author_id=user_id, + date_create=datetime.utcnow()) + query = attachments.insert().values(**dict(attachment)) + attachment_id = await database.execute(query) + return await get_attachment_db(attachment_id) + + +async def upload_attachment(appeal_id: int, file: UploadFile, user: UserTable) -> Optional[AttachmentDB]: + filename = f'{datetime.utcnow()}-{file.filename}' + with open(f'/attachments/{filename}', 'wb') as buffer: + shutil.copyfileobj(file.file, buffer) + await notify_attachment(appeal_id, user) + return await add_attachment(appeal_id, filename, str(user.id)) + + +async def delete_attachment(attachment_id: int) -> None: + query = attachments.delete().where(attachments.c.id == attachment_id) + await database.execute(query) + + +async def notify_create_appeal(appeal_id: int, user: UserTable) -> None: + appeal = await get_appeal_db(appeal_id) + client = await get_client_db(appeal.client_id) + message = f"Представителем закачика {client.name} - {user.name} {user.surname} " \ + f"было создано обращение №{appeal_id} - {appeal.topic}" + developers = await get_developers() + for developer in developers: + await notify_user(developer.email, message) + return None + + +async def notify_update_appeal(appeal_id: int, user: UserTable) -> None: + appeal = await get_appeal_db(appeal_id) + message = f"Обращение №{appeal_id} - {appeal.topic} было измененно" + if user.is_superuser: + author = await get_or_404(UUID4(appeal.author_id)) + to_addr = author.email + message += " разработчиком" + await notify_user(to_addr, message) + elif appeal.responsible_id: + developer = await get_or_404(UUID4(appeal.responsible_id)) + to_addr = developer.email + message += " представителем заказчика" + await notify_user(to_addr, message) + return None + + +async def notify_comment(appeal_id: int, user: UserTable) -> None: + appeal = await get_appeal_db(appeal_id) + message = f"В обращении №{appeal_id} - {appeal.topic} был оставлен комментарий" + if user.is_superuser: + author = await get_or_404(UUID4(appeal.author_id)) + to_addr = author.email + await notify_user(to_addr, message) + elif appeal.responsible_id: + developer = await get_or_404(UUID4(appeal.responsible_id)) + to_addr = developer.email + await notify_user(to_addr, message) + return None + + +async def notify_attachment(appeal_id: int, user: UserTable) -> None: + appeal = await get_appeal_db(appeal_id) + message = f"В обращении №{appeal_id} - {appeal.topic} были изменены вложения" + if user.is_superuser: + author = await get_or_404(UUID4(appeal.author_id)) + to_addr = author.email + await notify_user(to_addr, message) + elif appeal.responsible_id: + developer = await get_or_404(UUID4(appeal.responsible_id)) + to_addr = developer.email + await notify_user(to_addr, message) + return None + + +async def notify_user(to_addr: str, message: str) -> None: + title = "Изменение обращения" + email = Email(recipient=to_addr, title=title, message=message) + await send_mail(email) From e3d7397ffbcad494f8eb6e73d3dc4a235305f0f1 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Tue, 1 Jun 2021 14:24:51 +0500 Subject: [PATCH 49/55] =?UTF-8?q?=D0=BF=D0=BE=D1=87=D0=B8=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D1=80=D0=B8=D1=81=D0=B2=D0=BE=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BE=D1=82=D1=87=D0=B5=D1=81=D1=82=D0=B2=D0=B0=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B2=D0=BB=D0=B0=D0=B4=D0=B5=D0=BB=D1=8C=D1=86=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/client_account/services.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index a4f9780..67b07fb 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -48,7 +48,7 @@ async def get_clients() -> List[ClientShort]: clients_list = [] for client in result: client = dict(client) - owner = await get_client_owner(client["id"]) + owner = await get_or_404(client["owner_id"]) count_employees = await get_count_employees(client["id"]) clients_list.append(ClientShort(**dict({**client, "owner": owner, "count_employees": count_employees}))) return clients_list @@ -85,14 +85,15 @@ async def get_dev_client_page(client_id: int) -> DevClientPage: async def get_client(client_id: int) -> Optional[Client]: query = clients.select().where(clients.c.id == client_id) - result = await database.fetch_one(query=query) - if result is None: + client = await database.fetch_one(query=query) + if client is None: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=Errors.CLIENT_NOT_FOUND) - owner = await get_client_owner(client_id) + client = dict(client) + owner = await get_or_404(client["owner_id"]) licences_list = await get_client_licences(client_id) - return Client(**dict({**dict(result), + return Client(**dict({**client, "owner": owner, "licences": licences_list})) @@ -122,6 +123,7 @@ async def add_client(data: ClientAndOwnerCreate) -> Optional[ClientDB]: password=data.password, name=data.owner_name, surname=data.surname, + patronymic=data.patronymic, avatar=data.owner_avatar, client_id=client_id, is_owner=True, From b23ccba584abc7be55e1707cf007ad7b321877a4 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Tue, 1 Jun 2021 14:52:28 +0500 Subject: [PATCH 50/55] =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B2=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/client_account/services.py | 6 ++++-- src/desk/routes.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index 67b07fb..1520bf1 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -50,7 +50,9 @@ async def get_clients() -> List[ClientShort]: client = dict(client) owner = await get_or_404(client["owner_id"]) count_employees = await get_count_employees(client["id"]) - clients_list.append(ClientShort(**dict({**client, "owner": owner, "count_employees": count_employees}))) + clients_list.append(ClientShort(**dict({**client, + "owner": owner, + "count_employees": count_employees}))) return clients_list @@ -91,7 +93,7 @@ async def get_client(client_id: int) -> Optional[Client]: status_code=status.HTTP_400_BAD_REQUEST, detail=Errors.CLIENT_NOT_FOUND) client = dict(client) - owner = await get_or_404(client["owner_id"]) + owner = await get_or_404(UUID4(str(client["owner_id"]))) licences_list = await get_client_licences(client_id) return Client(**dict({**client, "owner": owner, diff --git a/src/desk/routes.py b/src/desk/routes.py index d4a6f75..e9e943d 100644 --- a/src/desk/routes.py +++ b/src/desk/routes.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, status, Depends, Response, HTTPException, UploadFile +from fastapi import APIRouter, status, Depends, Response, HTTPException, UploadFile, File from typing import List, Union from .services import get_all_appeals, get_appeal, get_comments, get_comment, \ @@ -76,7 +76,7 @@ async def get_attachment_by_id(id: int, pk: int, user: UserTable = Depends(any_u @router.post("/{id}/attachments/", response_model=AttachmentDB, status_code=status.HTTP_201_CREATED) -async def upload_attachments(id: int, file: UploadFile, user: UserTable = Depends(any_user)): +async def upload_attachments(id: int, file: UploadFile = File(...), user: UserTable = Depends(any_user)): await check_access(id, user, status.HTTP_403_FORBIDDEN) return await upload_attachment(id, file, user) From ce6107598c424ad97d2a17628bc556fcb7311665 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Tue, 1 Jun 2021 19:04:02 +0500 Subject: [PATCH 51/55] =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BE=D1=81=D0=BF?= =?UTF-8?q?=D0=BE=D1=81=D0=BE=D0=B1=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D1=8B=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/desk/routes.py | 21 ++++--- src/desk/schemas.py | 8 +-- src/desk/services.py | 130 +++++++++++++++++++++++++++---------------- 3 files changed, 98 insertions(+), 61 deletions(-) diff --git a/src/desk/routes.py b/src/desk/routes.py index e9e943d..1e6c2fb 100644 --- a/src/desk/routes.py +++ b/src/desk/routes.py @@ -1,28 +1,31 @@ from fastapi import APIRouter, status, Depends, Response, HTTPException, UploadFile, File -from typing import List, Union +from typing import List, Union, Optional from .services import get_all_appeals, get_appeal, get_comments, get_comment, \ add_appeal, add_comment, update_appeal, update_comment, delete_comment, get_appeals_page, upload_attachment, \ - delete_attachment, get_attachment, update_dev_appeal, check_access + delete_attachment, get_attachment, update_dev_appeal, check_access, get_dev_appeal from .schemas import Appeal, CommentShort, Comment, AppealCreate, CommentCreate, CommentDB, \ - AppealUpdate, AppealDB, AppealShort, CommentUpdate, DevAppeal, AttachmentDB + AppealUpdate, AppealDB, AppealShort, CommentUpdate, DevAppeal, AttachmentDB, AppealList, AppealsPage from ..users.models import UserTable from ..users.logic import employee, any_user router = APIRouter() -@router.get("/", response_model=List[AppealShort], status_code=status.HTTP_200_OK) +@router.get("/", status_code=status.HTTP_200_OK) async def appeals_list(user: UserTable = Depends(any_user)): if user.is_superuser: return await get_all_appeals() return await get_appeals_page(user) -@router.get("/{id}", response_model=Union[Appeal, DevAppeal], status_code=status.HTTP_200_OK) +@router.get("/{id}", status_code=status.HTTP_200_OK) async def appeal(id: int, user: UserTable = Depends(any_user)): await check_access(id, user, status.HTTP_403_FORBIDDEN) - return await get_appeal(id, user) + result = await get_appeal(id, user) + if user.is_superuser: + result = await get_dev_appeal(id, user, result) + return result @router.post("/", response_model=AppealDB, status_code=status.HTTP_201_CREATED) @@ -33,10 +36,10 @@ async def create_appeal(item: AppealCreate, user: UserTable = Depends(employee)) @router.patch("/{id}", response_model=AppealDB, status_code=status.HTTP_201_CREATED) -async def update_appeal_by_id(appeal_id: int, item: AppealUpdate, user: UserTable = Depends(any_user)): +async def update_appeal_by_id(id: int, item: AppealUpdate, user: UserTable = Depends(any_user)): if user.is_superuser: - return await update_dev_appeal(appeal_id, item, user) - return await update_appeal(appeal_id, item, user) + return await update_dev_appeal(id, item, user) + return await update_appeal(id, item, user) @router.get("/{id}/comments", response_model=List[CommentShort], status_code=status.HTTP_200_OK) diff --git a/src/desk/schemas.py b/src/desk/schemas.py index fca1afa..527bac2 100644 --- a/src/desk/schemas.py +++ b/src/desk/schemas.py @@ -6,7 +6,7 @@ from ..accounts.client_account.schemas import ClientDB, Client from ..reference_book.schemas import SoftwareDB, ModuleDB -from ..users.schemas import UserDB +from ..users.schemas import UserDB, DeveloperList class CommentBase(BaseModel): @@ -66,6 +66,7 @@ class AppealBase(BaseModel): class AppealCreate(AppealBase): text: str + importance: Optional[int] software_id: int module_id: int date_create: datetime = datetime.utcnow() @@ -105,11 +106,10 @@ class AppealDB(AppealBase): class AppealList(AppealBase): id: int importance: int - title: str author: UserDB client: ClientDB date_create: datetime - responsible: UserDB + responsible: Optional[UserDB] status: StatusTasks software: SoftwareDB module: ModuleDB @@ -139,5 +139,5 @@ class AppealsPage(BaseModel): class DevAppeal(Appeal): software_list: List[SoftwareDB] modules_list: List[ModuleDB] - developers: List[UserDB] + developers: List[DeveloperList] allowed_statuses: List[StatusTasks] diff --git a/src/desk/services.py b/src/desk/services.py index 7b520f0..f774804 100644 --- a/src/desk/services.py +++ b/src/desk/services.py @@ -1,6 +1,6 @@ import shutil from datetime import datetime -from typing import List, Union, Optional +from typing import List, Union, Optional, Dict from pydantic.types import UUID4 @@ -27,32 +27,35 @@ async def get_all_appeals() -> List[AppealList]: appeals_list = [] for appeal in result: appeal = dict(appeal) - author = await get_or_404(appeal["author_id"]) - client = await get_client_db(appeal["client_id"]) - responsible = await get_or_404(appeal["responsible_id"]) - software = await get_software(appeal["software_id"]) - module = await get_module(appeal["module_id"]) - appeals_list.append(AppealList(**dict({ - **appeal, - "author": author, - "client": client, - "responsible": responsible, - "software": software, - "module": module}))) + appeals_list.append(await get_appeal_list(appeal)) return appeals_list -async def get_appeals(user: UserTable) -> List[Appeal]: +async def get_appeals(user: UserTable) -> List[AppealList]: query = appeals.select().where(appeals.c.client_id == user.client_id) result = await database.fetch_all(query=query) appeals_list = [] for appeal in result: appeal = dict(appeal) - correct_appeal = await get_appeal(appeal["id"], user) - appeals_list.append(Appeal(**dict(correct_appeal))) + appeals_list.append(await get_appeal_list(appeal)) return appeals_list +async def get_appeal_list(appeal: Dict) -> AppealList: + author = await get_or_404(appeal["author_id"]) + client = await get_client_db(appeal["client_id"]) + responsible = await get_user(appeal["responsible_id"]) + software = await get_software(appeal["software_id"]) + module = await get_module(appeal["module_id"]) + return AppealList(**dict({ + **appeal, + "author": author, + "client": client, + "responsible": responsible, + "software": software, + "module": module})) + + async def get_appeals_page(user: UserTable) -> AppealsPage: appeals_list = await get_appeals(user) client = await get_client(user.client_id) @@ -64,9 +67,8 @@ async def get_appeals_page(user: UserTable) -> AppealsPage: "modules_list": modules_list})) -async def get_appeal(appeal_id: int, user: UserTable) -> Union[Appeal, DevAppeal]: +async def get_appeal(appeal_id: int, user: UserTable) -> Appeal: appeal = await check_access(appeal_id, user, status.HTTP_404_NOT_FOUND) - # TODO проверить на несуществующее поле или на None client = await get_client_db(appeal.client_id) author = await get_user(UUID4(appeal.author_id)) responsible = None @@ -75,23 +77,26 @@ async def get_appeal(appeal_id: int, user: UserTable) -> Union[Appeal, DevAppeal software = await get_software(appeal.software_id) module = await get_module(appeal.module_id) comment = await get_comments(appeal_id, user) - result = Appeal(**dict({**appeal, + result = Appeal(**dict({**dict(appeal), "client": client, "author": author, "responsible": responsible, "software": software, "module": module, "comments": comment})) - if user.is_superuser: - developers = await get_developers() - allowed_statuses = await get_next_status(appeal_id, user) - modules_list = await get_modules() - software_list = await get_software_db_list() - result = DevAppeal(**dict({**result, - "software_list": software_list, - "modules_list": modules_list, - "developers": developers, - "allowed_statuses": allowed_statuses})) + return result + + +async def get_dev_appeal(appeal_id: int, user: UserTable, appeal: Appeal) -> DevAppeal: + developers = await get_developers() + allowed_statuses = await get_next_status(appeal_id, user) + modules_list = await get_modules() + software_list = await get_software_db_list() + result = DevAppeal(**dict({**dict(appeal), + "software_list": software_list, + "modules_list": modules_list, + "developers": developers, + "allowed_statuses": allowed_statuses})) return result @@ -105,6 +110,11 @@ async def get_appeal_db(appeal_id: int) -> AppealDB: async def add_appeal(appeal: AppealCreate, user: UserTable) -> AppealDB: + if appeal.importance: + if appeal.importance > 5: + appeal.importance = 5 + elif appeal.importance < 1: + appeal.importance = 1 item = {**appeal.dict(), "client_id": int(user.client_id), "author_id": str(user.id), "status": StatusTasks.new} query = appeals.insert().values(item) appeal_id = await database.execute(query) @@ -114,19 +124,30 @@ async def add_appeal(appeal: AppealCreate, user: UserTable) -> AppealDB: async def update_appeal(appeal_id: int, appeal: AppealUpdate, user: UserTable) -> AppealDB: old_appeal = await check_access(appeal_id, user, status.HTTP_403_FORBIDDEN) - if old_appeal.status == StatusTasks.closed or old_appeal.status == StatusTasks.canceled: - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=Errors.APPEAL_IS_CLOSED) appeal = appeal.dict(exclude_unset=True) + + if old_appeal.status == StatusTasks.closed or old_appeal.status == StatusTasks.canceled: + if "status" not in appeal.keys() or appeal["status"] != StatusTasks.reopen: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=Errors.APPEAL_IS_CLOSED) + if "importance" in appeal: - if appeal["importance"] < 0: + if appeal["importance"] < 1: appeal["importance"] = 1 if appeal["importance"] > 5: appeal["importance"] = 5 - old_appeal = dict(old_appeal) + old_appeal_dict = dict(old_appeal) for field in appeal: - if appeal[field] is not None: - old_appeal[field] = appeal[field] - query = appeals.update().where(appeals.c.id == appeal_id).values(old_appeal) + if field not in ["status", "text"]: + continue + if field == "status" and (appeal[field] != StatusTasks.reopen + or (old_appeal_dict["status"] != StatusTasks.canceled + and old_appeal_dict["status"] != StatusTasks.closed)): + continue + elif appeal[field] is not None: + old_appeal_dict[field] = appeal[field] + if old_appeal_dict != dict(old_appeal): + old_appeal_dict["date_edit"] = datetime.utcnow() + query = appeals.update().where(appeals.c.id == appeal_id).values(old_appeal_dict) result_id = await database.execute(query) await notify_update_appeal(appeal_id, user) return await get_appeal_db(appeal_id) @@ -134,24 +155,33 @@ async def update_appeal(appeal_id: int, appeal: AppealUpdate, user: UserTable) - async def update_dev_appeal(appeal_id: int, appeal: AppealUpdate, user: UserTable) -> AppealDB: old_appeal = await get_appeal_db(appeal_id) + if old_appeal.status == StatusTasks.closed or old_appeal.status == StatusTasks.canceled: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=Errors.APPEAL_IS_CLOSED) appeal = appeal.dict(exclude_unset=True) + if "status" in appeal and (appeal["status"] == StatusTasks.closed or appeal["status"] == StatusTasks.canceled): appeal["date_processing"] = datetime.utcnow() + if "responsible_id" in appeal and old_appeal.status == StatusTasks.new: appeal["status"] = StatusTasks.registered + if "importance" in appeal: - if appeal["importance"] < 0: + if appeal["importance"] < 1: appeal["importance"] = 1 if appeal["importance"] > 5: appeal["importance"] = 5 - old_appeal = dict(old_appeal) + + old_appeal_dict = dict(old_appeal) for field in appeal: if appeal[field] is not None: - old_appeal[field] = appeal[field] - query = appeals.update().where(appeals.c.id == appeal_id).values(old_appeal) - result_id = await database.execute(query) + old_appeal_dict[field] = appeal[field] + + if old_appeal_dict != dict(old_appeal): + old_appeal_dict["date_edit"] = datetime.utcnow() + + query = appeals.update().where(appeals.c.id == appeal_id).values(old_appeal_dict) + await database.execute(query) await notify_update_appeal(appeal_id, user) return await get_appeal_db(appeal_id) @@ -215,7 +245,7 @@ async def check_access(appeal_id: int, user: UserTable, status_code: status): return appeal -async def get_next_status(appeal_id: int, user: UserTable): +async def get_next_status(appeal_id: int, user: UserTable) -> List[StatusTasks]: appeal = await get_appeal_db(appeal_id) current_status = appeal.status if user.is_superuser: @@ -225,8 +255,12 @@ async def get_next_status(appeal_id: int, user: UserTable): return [StatusTasks.in_work] elif current_status is StatusTasks.in_work: return [StatusTasks.closed, StatusTasks.canceled] + elif current_status is StatusTasks.reopen: + return [StatusTasks.in_work] + else: + return [] elif current_status is StatusTasks.closed or current_status is StatusTasks.canceled: - return [StatusTasks.in_work] + return [StatusTasks.reopen] else: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=Errors.USER_CAN_NOT_CHANGE_STATUS) @@ -273,8 +307,8 @@ async def delete_attachment(attachment_id: int) -> None: async def notify_create_appeal(appeal_id: int, user: UserTable) -> None: appeal = await get_appeal_db(appeal_id) client = await get_client_db(appeal.client_id) - message = f"Представителем закачика {client.name} - {user.name} {user.surname} " \ - f"было создано обращение №{appeal_id} - {appeal.topic}" + message = f"Представителем закачика «{client.name}» - {user.name} {user.surname} " \ + f"было создано обращение №{appeal_id} - «{appeal.topic}»" developers = await get_developers() for developer in developers: await notify_user(developer.email, message) @@ -283,7 +317,7 @@ async def notify_create_appeal(appeal_id: int, user: UserTable) -> None: async def notify_update_appeal(appeal_id: int, user: UserTable) -> None: appeal = await get_appeal_db(appeal_id) - message = f"Обращение №{appeal_id} - {appeal.topic} было измененно" + message = f"Обращение №{appeal_id} - «{appeal.topic}» было измененно" if user.is_superuser: author = await get_or_404(UUID4(appeal.author_id)) to_addr = author.email @@ -299,7 +333,7 @@ async def notify_update_appeal(appeal_id: int, user: UserTable) -> None: async def notify_comment(appeal_id: int, user: UserTable) -> None: appeal = await get_appeal_db(appeal_id) - message = f"В обращении №{appeal_id} - {appeal.topic} был оставлен комментарий" + message = f"В обращении №{appeal_id} - «{appeal.topic}» был оставлен комментарий" if user.is_superuser: author = await get_or_404(UUID4(appeal.author_id)) to_addr = author.email @@ -313,7 +347,7 @@ async def notify_comment(appeal_id: int, user: UserTable) -> None: async def notify_attachment(appeal_id: int, user: UserTable) -> None: appeal = await get_appeal_db(appeal_id) - message = f"В обращении №{appeal_id} - {appeal.topic} были изменены вложения" + message = f"В обращении №{appeal_id} - «{appeal.topic}» были изменены вложения" if user.is_superuser: author = await get_or_404(UUID4(appeal.author_id)) to_addr = author.email From 5318ba09a574792cb2b3c5904f553cf479a1a19e Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Wed, 2 Jun 2021 00:55:04 +0500 Subject: [PATCH 52/55] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA?= =?UTF-8?q?=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/client_account/services.py | 7 +- src/accounts/developer_account/routers.py | 5 + .../developer_account/statistics/__init__.py | 0 .../developer_account/statistics/routers.py | 25 ++++ .../developer_account/statistics/schemas.py | 56 ++++++++ .../developer_account/statistics/services.py | 127 ++++++++++++++++++ src/desk/services.py | 102 ++++++++++++-- src/reference_book/api/licence.py | 2 +- src/reference_book/schemas.py | 5 +- src/reference_book/services.py | 17 ++- src/users/logic.py | 7 +- 11 files changed, 333 insertions(+), 20 deletions(-) create mode 100644 src/accounts/developer_account/statistics/__init__.py create mode 100644 src/accounts/developer_account/statistics/routers.py create mode 100644 src/accounts/developer_account/statistics/schemas.py create mode 100644 src/accounts/developer_account/statistics/services.py diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index 1520bf1..6237a7b 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -43,12 +43,17 @@ async def get_count_employees(client_id: int) -> int: return len(result) # TODO проверить не будет ли ошибки +async def get_clients_db() -> List[ClientDB]: + result = await database.fetch_all(clients.select()) + return [ClientDB(**dict(client)) for client in result] + + async def get_clients() -> List[ClientShort]: result = await database.fetch_all(clients.select()) clients_list = [] for client in result: client = dict(client) - owner = await get_or_404(client["owner_id"]) + owner = await get_or_404(UUID4(str(client["owner_id"]))) count_employees = await get_count_employees(client["id"]) clients_list.append(ClientShort(**dict({**client, "owner": owner, diff --git a/src/accounts/developer_account/routers.py b/src/accounts/developer_account/routers.py index ad7bfba..d51fcbe 100644 --- a/src/accounts/developer_account/routers.py +++ b/src/accounts/developer_account/routers.py @@ -7,6 +7,8 @@ from src.users.models import UserTable from src.users.logic import developer_user, get_developers, change_pwd, pre_update_developer, get_email_with_changed_pwd from src.users.schemas import UserDB, UserUpdate, DeveloperList, DeveloperCreate +from .statistics.routers import statistics_router + developer_router = APIRouter() @@ -43,3 +45,6 @@ async def change_dev_pwd(id: UUID4, new_pwd: str, user: UserTable = Depends(deve @developer_router.delete("/{id:uuid}", response_class=Response, status_code=status.HTTP_204_NO_CONTENT) async def delete_developer_by_id(id: UUID4, user: UserTable = Depends(developer_user)): await delete_developer(id) + + +developer_router.include_router(statistics_router, prefix='/statistics', tags=['Statistics']) diff --git a/src/accounts/developer_account/statistics/__init__.py b/src/accounts/developer_account/statistics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/accounts/developer_account/statistics/routers.py b/src/accounts/developer_account/statistics/routers.py new file mode 100644 index 0000000..ef9df0d --- /dev/null +++ b/src/accounts/developer_account/statistics/routers.py @@ -0,0 +1,25 @@ +from fastapi import APIRouter, Depends, status + +from src.accounts.developer_account.statistics.schemas import DevelopersStatistics, ClientsStatistics, AppealsStatistics +from src.accounts.developer_account.statistics.services import get_developers_statistics, get_clients_statistics, \ + get_appeals_statistics +from src.users.logic import developer_user +from src.users.models import UserTable + + +statistics_router = APIRouter() + + +@statistics_router.get("/appeals", response_model=AppealsStatistics, status_code=status.HTTP_200_OK) +async def developers_list(user: UserTable = Depends(developer_user)): + return await get_appeals_statistics() + + +@statistics_router.get("/clients", response_model=ClientsStatistics, status_code=status.HTTP_200_OK) +async def developer(user: UserTable = Depends(developer_user)): + return await get_clients_statistics() + + +@statistics_router.get("/developers", response_model=DevelopersStatistics, status_code=status.HTTP_200_OK) +async def developer(user: UserTable = Depends(developer_user)): + return await get_developers_statistics() diff --git a/src/accounts/developer_account/statistics/schemas.py b/src/accounts/developer_account/statistics/schemas.py new file mode 100644 index 0000000..48aa5c0 --- /dev/null +++ b/src/accounts/developer_account/statistics/schemas.py @@ -0,0 +1,56 @@ +from typing import List + +from pydantic.main import BaseModel + + +class SoftwareStatistics(BaseModel): + name: str + count_appeals: int + + +class ModuleStatistics(BaseModel): + name: str + count_appeals: int + + +class StatusStatistics(BaseModel): + name: str + count_appeals: int + + +class AppealsStatistics(BaseModel): + software_list: List[SoftwareStatistics] + modules_list: List[ModuleStatistics] + statuses_list: List[StatusStatistics] + + +class ClientStatistics(BaseModel): + name: str + count_appeals: int + open_statuses: int + closed: int + canceled: int + + +class ClientsStatistics(BaseModel): + clients_list: List[ClientStatistics] + + +class DeveloperStatistics(BaseModel): + name: str + count_appeals: int + open_statuses: int + closed_statuses: int + + +class DevelopersStatistics(BaseModel): + developers_list: List[DeveloperStatistics] + + +class StatusesDistribution(BaseModel): + new: int + registered: int + in_work: int + closed: int + canceled: int + reopen: int diff --git a/src/accounts/developer_account/statistics/services.py b/src/accounts/developer_account/statistics/services.py new file mode 100644 index 0000000..9842744 --- /dev/null +++ b/src/accounts/developer_account/statistics/services.py @@ -0,0 +1,127 @@ +from typing import List + +from src.accounts.client_account.services import get_clients_db +from src.accounts.developer_account.statistics.schemas import AppealsStatistics, ClientsStatistics, \ + DevelopersStatistics, DeveloperStatistics, SoftwareStatistics, ModuleStatistics, StatusStatistics, ClientStatistics +from src.desk.services import get_appeals_by_developer, get_appeals_by_client, \ + get_appeals_by_software, get_appeals_by_module, get_appeals_db, get_status_distribution, get_statuses_list +from src.reference_book.services import get_software_db_list, get_modules +from src.users.logic import get_developers_db + + +async def get_software_list_stat() -> List[SoftwareStatistics]: + softwares = await get_software_db_list() + software_list = [] + for software in softwares: + appeals = await get_appeals_by_software(software.id) + count_appeals = len(appeals) + software_list.append(SoftwareStatistics(name=software.name, count_appeals=count_appeals)) + return software_list + + +async def get_module_list_stat() -> List[ModuleStatistics]: + modules = await get_modules() + modules_list = [] + for module in modules: + appeals = await get_appeals_by_module(module.id) + count_appeals = len(appeals) + modules_list.append(ModuleStatistics(name=module.name, count_appeals=count_appeals)) + return modules_list + + +async def get_status_list_stat() -> List[StatusStatistics]: + appeals = await get_appeals_db() + return await get_statuses_list(appeals) + + +async def get_appeals_statistics() -> AppealsStatistics: + software_list = await get_software_list_stat() + modules_list = await get_module_list_stat() + statuses_list = await get_status_list_stat() + await quick_sort(software_list) + await quick_sort(modules_list) + await quick_sort(statuses_list) + software_list = software_list[::-1] + modules_list = modules_list[::-1] + statuses_list = statuses_list[::-1] + return AppealsStatistics(software_list=software_list, + modules_list=modules_list, + statuses_list=statuses_list) + + +async def get_clients_statistics() -> ClientsStatistics: + clients = await get_clients_db() + clients_list = [] + for client in clients: + appeals = await get_appeals_by_client(client.id) + statuses = await get_status_distribution(appeals) + count_appeals = len(appeals) + open_statuses = statuses.registered + statuses.in_work + statuses.reopen + closed = statuses.closed + canceled = statuses.canceled + clients_list.append(ClientStatistics( + name=client.name, + count_appeals=count_appeals, + open_statuses=open_statuses, + closed=closed, + canceled=canceled + )) + await quick_sort(clients_list) + clients_list = clients_list[::-1] + return ClientsStatistics(clients_list=clients_list) + + +async def get_developers_statistics() -> DevelopersStatistics: + developers = await get_developers_db() + developers_list = [] + for developer in developers: + appeals = await get_appeals_by_developer(str(developer.id)) + statuses = await get_status_distribution(appeals) + count_appeals = len(appeals) + open_statuses = statuses.registered + statuses.in_work + statuses.reopen + closed_statuses = statuses.canceled + statuses.closed + developers_list.append(DeveloperStatistics( + name=developer.name, + count_appeals=count_appeals, + open_statuses=open_statuses, + closed_statuses=closed_statuses + )) + await quick_sort(developers_list) + developers_list = developers_list[::-1] + return DevelopersStatistics(developers_list=developers_list) + + +async def partition(nums, low, high): + # Выбираем средний элемент в качестве опорного + # Также возможен выбор первого, последнего + # или произвольного элементов в качестве опорного + pivot = nums[(low + high) // 2].count_appeals + i = low - 1 + j = high + 1 + while True: + i += 1 + while nums[i].count_appeals < pivot: + i += 1 + + j -= 1 + while nums[j].count_appeals > pivot: + j -= 1 + + if i >= j: + return j + + # Если элемент с индексом i (слева от опорного) больше, чем + # элемент с индексом j (справа от опорного), меняем их местами + nums[i], nums[j] = nums[j], nums[i] + + +async def quick_sort(nums): + # Создадим вспомогательную функцию, которая вызывается рекурсивно + async def _quick_sort(items, low, high): + if low < high: + # This is the index after the pivot, where our lists are split + split_index = await partition(items, low, high) + await _quick_sort(items, low, split_index) + await _quick_sort(items, split_index + 1, high) + + await _quick_sort(nums, 0, len(nums) - 1) diff --git a/src/desk/services.py b/src/desk/services.py index f774804..ec1a49b 100644 --- a/src/desk/services.py +++ b/src/desk/services.py @@ -1,19 +1,18 @@ import shutil from datetime import datetime -from typing import List, Union, Optional, Dict +from typing import List, Optional, Dict from pydantic.types import UUID4 -from .schemas import AppealCreate, CommentCreate, AppealUpdate, CommentUpdate, AppealList, AppealDB, Appeal, DevAppeal, \ - AppealsPage, AttachmentDB, AttachmentCreate -from ..accounts.client_account.models import clients +from .schemas import AppealCreate, CommentCreate, AppealUpdate, CommentUpdate, AppealList, AppealDB, Appeal, \ + DevAppeal, AppealsPage, AttachmentDB, AttachmentCreate from ..accounts.client_account.services import get_client_db, get_client +from ..accounts.developer_account.statistics.schemas import StatusesDistribution, StatusStatistics from ..db.db import database from .models import appeals, comments, attachments from ..errors import Errors -from ..reference_book.models import softwares, modules -from ..reference_book.services import get_software, get_module, get_modules, get_software_list, get_software_db_list -from ..users.logic import get_developers, get_or_404, get_user, get_client_users +from ..reference_book.services import get_software, get_module, get_modules, get_software_db_list +from ..users.logic import get_developers, get_or_404, get_user from ..users.models import UserTable, users from .models import StatusTasks from ..service import check_dict, send_mail, Email @@ -41,6 +40,91 @@ async def get_appeals(user: UserTable) -> List[AppealList]: return appeals_list +async def get_appeals_by_developer(developer_id: str) -> List[AppealDB]: + query = appeals.select().where(appeals.c.responsible_id == developer_id) + result = await database.fetch_all(query=query) + return [AppealDB(**dict(appeal)) for appeal in result] + + +async def get_appeals_by_client(client_id: int) -> List[AppealDB]: + query = appeals.select().where(appeals.c.client_id == client_id) + result = await database.fetch_all(query=query) + return [AppealDB(**dict(appeal)) for appeal in result] + + +async def get_appeals_by_software(software_id: int) -> List[AppealDB]: + query = appeals.select().where(appeals.c.software_id == software_id) + result = await database.fetch_all(query=query) + return [AppealDB(**dict(appeal)) for appeal in result] + + +async def get_appeals_by_module(module_id: int) -> List[AppealDB]: + query = appeals.select().where(appeals.c.module_id == module_id) + result = await database.fetch_all(query=query) + return [AppealDB(**dict(appeal)) for appeal in result] + + +async def get_appeals_db() -> List[AppealDB]: + result = await database.fetch_all(appeals.select()) + return [AppealDB(**dict(appeal)) for appeal in result] + + +async def get_status_distribution(appeals_list: List[AppealDB]) -> StatusesDistribution: + new = 0 + registered = 0 + in_work = 0 + closed = 0 + canceled = 0 + reopen = 0 + for appeal in appeals_list: + if appeal.status == StatusTasks.new: + new += 1 + elif appeal.status == StatusTasks.registered: + registered += 1 + elif appeal.status == StatusTasks.in_work: + in_work += 1 + elif appeal.status == StatusTasks.closed: + closed += 1 + elif appeal.status == StatusTasks.canceled: + canceled += 1 + elif appeal.status == StatusTasks.reopen: + reopen += 1 + return StatusesDistribution(new=new, + registered=registered, + in_work=in_work, + closed=closed, + canceled=canceled, + reopen=reopen) + + +async def get_statuses_list(appeals_list: List[AppealDB]) -> List[StatusStatistics]: + statuses_list = [] + statuses = { + "new": 0, + "registered": 0, + "in_work": 0, + "closed": 0, + "canceled": 0, + "reopen": 0, + } + for appeal in appeals_list: + if appeal.status == StatusTasks.new: + statuses["new"] += 1 + elif appeal.status == StatusTasks.registered: + statuses["registered"] += 1 + elif appeal.status == StatusTasks.in_work: + statuses["in_work"] += 1 + elif appeal.status == StatusTasks.closed: + statuses["closed"] += 1 + elif appeal.status == StatusTasks.canceled: + statuses["canceled"] += 1 + elif appeal.status == StatusTasks.reopen: + statuses["reopen"] += 1 + for current_status in statuses: + statuses_list.append(StatusStatistics(name=current_status, count_appeals=statuses[current_status])) + return statuses_list + + async def get_appeal_list(appeal: Dict) -> AppealList: author = await get_or_404(appeal["author_id"]) client = await get_client_db(appeal["client_id"]) @@ -141,14 +225,14 @@ async def update_appeal(appeal_id: int, appeal: AppealUpdate, user: UserTable) - continue if field == "status" and (appeal[field] != StatusTasks.reopen or (old_appeal_dict["status"] != StatusTasks.canceled - and old_appeal_dict["status"] != StatusTasks.closed)): + and old_appeal_dict["status"] != StatusTasks.closed)): continue elif appeal[field] is not None: old_appeal_dict[field] = appeal[field] if old_appeal_dict != dict(old_appeal): old_appeal_dict["date_edit"] = datetime.utcnow() query = appeals.update().where(appeals.c.id == appeal_id).values(old_appeal_dict) - result_id = await database.execute(query) + await database.execute(query) await notify_update_appeal(appeal_id, user) return await get_appeal_db(appeal_id) diff --git a/src/reference_book/api/licence.py b/src/reference_book/api/licence.py index 872bf10..fe554f1 100644 --- a/src/reference_book/api/licence.py +++ b/src/reference_book/api/licence.py @@ -26,7 +26,7 @@ async def create_licence(licence: LicenceCreate, user: UserTable = Depends(devel return await add_licence(licence) -@router.put("/{id}", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) +@router.patch("/{id}", response_model=LicenceDB, status_code=status.HTTP_201_CREATED) async def update_licence_by_id(id: int, item: LicenceUpdate, user: UserTable = Depends(developer_user)): return await update_licence(id, item) diff --git a/src/reference_book/schemas.py b/src/reference_book/schemas.py index fef31f8..d810865 100644 --- a/src/reference_book/schemas.py +++ b/src/reference_book/schemas.py @@ -60,8 +60,9 @@ class LicenceCreate(LicenceBase): software_id: int -class LicenceUpdate(BaseModel): - pass +class LicenceUpdate(LicenceBase): + count_members: Optional[int] + date_end: Optional[datetime] class LicenceDB(LicenceBase): diff --git a/src/reference_book/services.py b/src/reference_book/services.py index a01deb6..fb5dc23 100644 --- a/src/reference_book/services.py +++ b/src/reference_book/services.py @@ -1,5 +1,4 @@ from fastapi import HTTPException, status -from pydantic.types import UUID4 from .schemas import ModuleCreate, LicenceCreate, SoftwareCreate, EmployeeLicenceCreate, EmployeeLicenceUpdate, \ SoftwareUpdate, SoftwareDB, Software, ModuleDB, ModuleUpdate, LicenceDB, Licence, LicenceUpdate, \ @@ -247,14 +246,14 @@ async def get_licence_db(licence_id: int) -> Optional[LicenceDB]: async def get_licence_by_number(licence_number: int) -> Optional[LicenceDB]: - result = await database.fetch_one(licences.select().where(licences.c.id == licence_number)) + result = await database.fetch_one(licences.select().where(licences.c.number == int(licence_number))) if result: return LicenceDB(**dict(result)) return None async def add_licence(licence: LicenceCreate) -> LicenceDB: - if await get_licence_by_number(licence.number) is not None: + if await get_licence_by_number(licence.number): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=Errors.LICENCE_IS_EXIST, @@ -270,13 +269,19 @@ async def add_licence(licence: LicenceCreate) -> LicenceDB: async def update_licence(licence_id: int, licence: LicenceUpdate) -> LicenceDB: - if await get_licence_db(licence_id) is None: + old_licence = await get_licence_db(licence_id) + if old_licence is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=Errors.LICENCE_IS_NOT_EXIST, ) - query = licences.update().where(licences.c.id == licence_id).values(**licence.dict()) - await database.execute(query) + licence = dict(licence) + old_licence = dict(old_licence) + for field in licence: + if licence[field]: + old_licence[field] = licence[field] + query = licences.update().where(licences.c.id == licence_id).values(**old_licence) + result = await database.execute(query=query) return await get_licence_db(licence_id) diff --git a/src/users/logic.py b/src/users/logic.py index d9123c8..1af759b 100644 --- a/src/users/logic.py +++ b/src/users/logic.py @@ -92,13 +92,18 @@ async def get_count_dev_appeals(developer_id: str) -> int: return len(result) # TODO проверить не будет ли ошибки на len(None) +async def get_developers_db() -> List[UserDB]: + query = users.select().where(users.c.is_superuser == 1) + result = await database.fetch_all(query=query) + return [UserDB(**dict(developer)) for developer in result] + + async def get_developers() -> List[DeveloperList]: query = users.select().where(users.c.is_superuser == 1) result = await database.fetch_all(query=query) developers = [] for developer in result: developer = dict(developer) - print(developer) count_appeals = await get_count_dev_appeals(str(developer["id"])) developers.append(DeveloperList(**dict({**developer, "count_appeals": count_appeals}))) return developers From 3dd73e9a3bdf3ed73ef158d2cf0db3beea5d7324 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Wed, 2 Jun 2021 11:55:31 +0500 Subject: [PATCH 53/55] =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B8,=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BA=D1=80=D1=8B=D0=BB=20TODO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/client_account/routers.py | 2 +- src/accounts/client_account/schemas.py | 2 +- src/accounts/client_account/services.py | 9 ++++----- src/reference_book/api/routes.py | 2 +- src/reference_book/schemas.py | 2 +- src/reference_book/services.py | 9 +++------ src/users/logic.py | 2 +- src/users/models.py | 2 +- src/users/schemas.py | 6 +++--- 9 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/accounts/client_account/routers.py b/src/accounts/client_account/routers.py index bd0ce0b..51454a6 100644 --- a/src/accounts/client_account/routers.py +++ b/src/accounts/client_account/routers.py @@ -24,7 +24,7 @@ async def create_client(item: ClientAndOwnerCreate, user: UserTable = Depends(de return await add_client(item) -@client_router.get("/{id}", response_model=Union[ClientPage, DevClientPage], status_code=status.HTTP_200_OK) +@client_router.get("/{id}", status_code=status.HTTP_200_OK) async def client(id: int, user: UserTable = Depends(any_user)): if user.is_superuser: return await get_dev_client_page(id) diff --git a/src/accounts/client_account/schemas.py b/src/accounts/client_account/schemas.py index f6e4c7e..24966f0 100644 --- a/src/accounts/client_account/schemas.py +++ b/src/accounts/client_account/schemas.py @@ -75,7 +75,7 @@ class ClientPage(BaseModel): class DevClientPage(BaseModel): client: Client - employees_list: List[Employee] = [] + employees_list: List[EmployeeList] = [] software_list: List[SoftwareDB] diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index 6237a7b..756b4c6 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -23,7 +23,7 @@ async def get_count_appeals(employee_id: str) -> int: query = appeals.select().where(appeals.c.author_id == employee_id) result = await database.fetch_all(query=query) - return len([dict(appeal) for appeal in result]) + return len(result) async def get_employees(client_id: int) -> List[EmployeeList]: @@ -39,8 +39,7 @@ async def get_employees(client_id: int) -> List[EmployeeList]: async def get_count_employees(client_id: int) -> int: result = await database.fetch_all(users.select().where(users.c.client_id == client_id)) - # result = [dict(employee) for employee in employees_data] - return len(result) # TODO проверить не будет ли ошибки + return len(result) async def get_clients_db() -> List[ClientDB]: @@ -137,12 +136,12 @@ async def add_client(data: ClientAndOwnerCreate) -> Optional[ClientDB]: date_reg=datetime.utcnow(), ) owner = await add_owner(client_id, owner) - owner_licence = await add_employee_licence(str(owner.id), data.owner_licence) + await add_employee_licence(str(owner.id), data.owner_licence) new_client = await get_client_db(client_id) return new_client -async def update_client(client_id: int, client: ClientUpdate) -> Optional[ClientDB]: # TODO проверить работу обновления +async def update_client(client_id: int, client: ClientUpdate) -> Optional[ClientDB]: new_client_dict = dict(client) old_client_dict = dict(await get_client_db(client_id)) for field in new_client_dict: diff --git a/src/reference_book/api/routes.py b/src/reference_book/api/routes.py index ea171ba..1473269 100644 --- a/src/reference_book/api/routes.py +++ b/src/reference_book/api/routes.py @@ -9,7 +9,7 @@ @router.get('/', status_code=status.HTTP_200_OK) -async def get_reference_book(user: UserTable = Depends(developer_user)): # TODO разобраться с доступом +async def get_reference_book(user: UserTable = Depends(developer_user)): licences = await licence_list(user) modules = await modules_list(user) softwares = await software_list(user) diff --git a/src/reference_book/schemas.py b/src/reference_book/schemas.py index d810865..8f37e12 100644 --- a/src/reference_book/schemas.py +++ b/src/reference_book/schemas.py @@ -74,7 +74,7 @@ class LicenceDB(LicenceBase): class Licence(LicenceBase): id: int number: int - closed_vacancies: int = -1 # TODO значение -1 для теста + closed_vacancies: int = -1 software: SoftwareDB diff --git a/src/reference_book/services.py b/src/reference_book/services.py index fb5dc23..078c845 100644 --- a/src/reference_book/services.py +++ b/src/reference_book/services.py @@ -91,7 +91,7 @@ async def update_software(software_id: int, software: SoftwareUpdate) -> Softwar return SoftwareDB(**dict({"id": software_id, **software.dict()})) -async def delete_software(software_id: int) -> None: # TODO safe delete +async def delete_software(software_id: int) -> None: if get_software(software_id) is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -242,7 +242,7 @@ async def get_licence_db(licence_id: int) -> Optional[LicenceDB]: result = await database.fetch_one(licences.select().where(licences.c.id == licence_id)) if result: return LicenceDB(**dict(result)) - return None # TODO может быть стоит бросать ошибку если не нашли запись + return None async def get_licence_by_number(licence_number: int) -> Optional[LicenceDB]: @@ -298,10 +298,7 @@ async def delete_licence(licence_id: int) -> None: async def get_count_employee_for_licence_id(licence_id: int) -> int: query = employee_licences.select().where(employee_licences.c.licence_id == licence_id) result = await database.fetch_all(query=query) - if result: - print(result) # TODO посмотреть можно ли просто вывести len() без преобразования - return len([dict(licence) for licence in result]) - return 0 + return len(result) async def get_free_vacancy_in_licence(licence_id: int) -> int: diff --git a/src/users/logic.py b/src/users/logic.py index 1af759b..a3101fe 100644 --- a/src/users/logic.py +++ b/src/users/logic.py @@ -89,7 +89,7 @@ async def after_verification(user: UserDB, request: Request): async def get_count_dev_appeals(developer_id: str) -> int: query = appeals.select().where(appeals.c.responsible_id == developer_id) result = await database.fetch_all(query=query) - return len(result) # TODO проверить не будет ли ошибки на len(None) + return len(result) async def get_developers_db() -> List[UserDB]: diff --git a/src/users/models.py b/src/users/models.py index 43ede19..da150d9 100644 --- a/src/users/models.py +++ b/src/users/models.py @@ -13,7 +13,7 @@ class UserTable(Base, SQLAlchemyBaseUserTable): patronymic = Column(String, nullable=True) avatar = Column(String, nullable=True) is_owner = Column(Boolean, default=False, nullable=True) - client_id = Column(Integer, ForeignKey('client.id')) # TODO сделать проверку на существующего владельца клиента + client_id = Column(Integer, ForeignKey('client.id')) date_reg = Column(DateTime(timezone=True), server_default=sql.func.now()) date_block = Column(DateTime, default=None, nullable=True) diff --git a/src/users/schemas.py b/src/users/schemas.py index 2a7e6c1..6d23655 100644 --- a/src/users/schemas.py +++ b/src/users/schemas.py @@ -2,7 +2,7 @@ import random from fastapi_users import models -from pydantic import validator, EmailStr +from pydantic import validator, EmailStr, BaseModel from typing import Optional from src.reference_book.schemas import LicenceDB @@ -77,10 +77,10 @@ class UserUpdate(User, models.BaseUserUpdate): class UserDB(User, models.BaseUserDB): - is_active: bool = True # TODO проверить зачем здесь был " = True" + is_active: bool = True is_owner: Optional[bool] client_id: Optional[int] - date_reg: datetime = datetime.utcnow() # TODO проверить можно ли убрать " = datetime.utcnow()" + date_reg: datetime = datetime.utcnow() date_block: Optional[datetime] From 5f16dab83bb553d92dd2468a882908718208d5ec Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Wed, 2 Jun 2021 12:38:21 +0500 Subject: [PATCH 54/55] =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D1=83=D0=BD=D0=B8=D0=BA=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D0=B7=D0=B0=D0=BA=D0=B0=D0=B7=D1=87=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/client_account/services.py | 9 +++++++-- src/accounts/employee_account/services.py | 1 + src/users/logic.py | 14 ++++++++------ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index 756b4c6..7e01fb9 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -14,7 +14,7 @@ from ...reference_book.schemas import LicenceDB from ...reference_book.services import add_client_licence, get_client_licences, get_software_db_list, \ add_employee_licence, get_employee_licence, get_licences_db -from ...users.logic import all_users, get_or_404, pre_update_user, user_is_active +from ...users.logic import all_users, get_or_404, pre_update_user, user_is_active, get_user_by_email from ...users.models import users from ...users.schemas import UserCreate, OwnerCreate, Employee, UserDB, EmployeeList, EmployeeUpdate from ...service import send_mail, Email @@ -52,7 +52,7 @@ async def get_clients() -> List[ClientShort]: clients_list = [] for client in result: client = dict(client) - owner = await get_or_404(UUID4(str(client["owner_id"]))) + owner = await get_or_404(UUID4(client["owner_id"])) count_employees = await get_count_employees(client["id"]) clients_list.append(ClientShort(**dict({**client, "owner": owner, @@ -115,6 +115,11 @@ async def get_client_db(client_id: int) -> Optional[ClientDB]: async def add_client(data: ClientAndOwnerCreate) -> Optional[ClientDB]: client = ClientCreate(**dict(data)) query = clients.insert().values({**client.dict(), "is_active": False, "owner_id": "undefined"}) + if await get_user_by_email(data.email): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=Errors.COMPANY_IS_EXIST, + ) try: client_id = await database.execute(query) except Exception: diff --git a/src/accounts/employee_account/services.py b/src/accounts/employee_account/services.py index e90d563..2e74722 100644 --- a/src/accounts/employee_account/services.py +++ b/src/accounts/employee_account/services.py @@ -60,6 +60,7 @@ async def send_mail_with_pwd(user: PreEmployeeCreate) -> None: async def delete_employee(pk: UUID4): + # TODO заменить в обращениях пользователя автора на владельца компании await delete_user(pk) diff --git a/src/users/logic.py b/src/users/logic.py index a3101fe..39da034 100644 --- a/src/users/logic.py +++ b/src/users/logic.py @@ -190,13 +190,11 @@ async def create_developer(): return created_developer -async def get_user_by_email(email: EmailStr) -> UserDB: +async def get_user_by_email(email: EmailStr) -> Optional[UserDB]: user = await database.fetch_one(query=users.select().where(users.c.email == email)) - if user is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=Errors.USER_NOT_FOUND) - return user + if user: + return user + return None async def change_pwd(user_id: UUID4, pwd: str, email: Email) -> UserDB: @@ -218,6 +216,10 @@ async def get_email_with_changed_pwd(pwd: str, user: UserTable) -> Email: async def get_new_password(email: EmailStr) -> UserDB: user = await get_user_by_email(email) + if user is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=Errors.USER_NOT_FOUND) pwd = generate_pwd() message = f"Добрый день!\nВаш новый пароль: {pwd}\n" \ f"Если Вы не запрашивали смену пароля обратитесь к администратору или смените его самостоятельно" From 90894ae1f99a8682c48dc50d8eaadb8e4b5bf186 Mon Sep 17 00:00:00 2001 From: Puzanovim Date: Wed, 2 Jun 2021 13:59:11 +0500 Subject: [PATCH 55/55] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=B3=D0=B8=D0=BD=D0=B0=D1=86=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/client_account/routers.py | 25 ++++++-------- src/accounts/client_account/services.py | 26 ++++++++------ src/accounts/developer_account/routers.py | 7 ++-- .../developer_account/statistics/services.py | 4 +-- src/accounts/employee_account/routers.py | 6 ---- src/accounts/employee_account/services.py | 6 ++-- src/desk/routes.py | 12 +++---- src/desk/services.py | 27 ++++++++------- src/reference_book/api/licence.py | 8 ++--- src/reference_book/api/module.py | 6 ++-- src/reference_book/api/routes.py | 6 ++-- src/reference_book/api/software.py | 6 ++-- src/reference_book/services.py | 34 +++++++++++-------- src/users/logic.py | 9 +++-- src/users/routers.py | 18 +--------- 15 files changed, 95 insertions(+), 105 deletions(-) diff --git a/src/accounts/client_account/routers.py b/src/accounts/client_account/routers.py index 51454a6..eed063a 100644 --- a/src/accounts/client_account/routers.py +++ b/src/accounts/client_account/routers.py @@ -1,22 +1,19 @@ -from typing import List, Union +from fastapi import APIRouter, Depends, status, HTTPException +from pydantic.types import UUID4 -from fastapi import APIRouter, Depends, status, Request, HTTPException -from .schemas import ClientDB, Client, ClientCreate, ClientUpdate, ClientAndOwnerCreate, ClientsPage, ClientPage, \ - DevClientPage -from .services import get_clients, get_client, add_client, update_client, add_owner, get_client_owner, update_client_owner, \ - get_clients_page, get_client_page, block_client, get_dev_client_page, get_client_info +from .schemas import ClientDB, Client, ClientUpdate, ClientAndOwnerCreate, ClientsPage +from .services import add_client, update_client, get_clients_page, get_client_page, \ + block_client, get_dev_client_page, get_client_info from src.users.models import UserTable -from src.users.logic import developer_user, any_user, get_client_users_with_superuser, get_owner_with_superuser, \ - get_client_users +from src.users.logic import developer_user, any_user, get_owner_with_superuser, get_client_users, default_uuid from ..employee_account.routers import employee_router -from ...users.schemas import UserCreate client_router = APIRouter() @client_router.get("/", response_model=ClientsPage, status_code=status.HTTP_200_OK) -async def clients_list(user: UserTable = Depends(developer_user)): - return await get_clients_page() +async def clients_list(user: UserTable = Depends(developer_user), last_id: int = 0, limit: int = 9): + return await get_clients_page(last_id, limit) @client_router.post("/", response_model=ClientDB, status_code=status.HTTP_201_CREATED) @@ -25,11 +22,11 @@ async def create_client(item: ClientAndOwnerCreate, user: UserTable = Depends(de @client_router.get("/{id}", status_code=status.HTTP_200_OK) -async def client(id: int, user: UserTable = Depends(any_user)): +async def client(id: int, user: UserTable = Depends(any_user), last_id: UUID4 = default_uuid, limit: int = 9): if user.is_superuser: - return await get_dev_client_page(id) + return await get_dev_client_page(id, last_id, limit) elif await get_client_users(id, user): - return await get_client_page(id) + return await get_client_page(id, last_id, limit) else: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) diff --git a/src/accounts/client_account/services.py b/src/accounts/client_account/services.py index 7e01fb9..168c3c9 100644 --- a/src/accounts/client_account/services.py +++ b/src/accounts/client_account/services.py @@ -4,6 +4,7 @@ from fastapi import HTTPException, status from fastapi_users.router import ErrorCode from pydantic.types import UUID4 +from sqlalchemy import desc from .schemas import ClientCreate, ClientUpdate, ClientAndOwnerCreate, ClientsPage, ClientShort, ClientPage, ClientDB, \ Client, DevClientPage @@ -14,7 +15,7 @@ from ...reference_book.schemas import LicenceDB from ...reference_book.services import add_client_licence, get_client_licences, get_software_db_list, \ add_employee_licence, get_employee_licence, get_licences_db -from ...users.logic import all_users, get_or_404, pre_update_user, user_is_active, get_user_by_email +from ...users.logic import all_users, get_or_404, pre_update_user, user_is_active, get_user_by_email, default_uuid from ...users.models import users from ...users.schemas import UserCreate, OwnerCreate, Employee, UserDB, EmployeeList, EmployeeUpdate from ...service import send_mail, Email @@ -26,8 +27,10 @@ async def get_count_appeals(employee_id: str) -> int: return len(result) -async def get_employees(client_id: int) -> List[EmployeeList]: - result = await database.fetch_all(users.select().where(users.c.client_id == client_id)) +async def get_employees(client_id: int, last_id: UUID4 = default_uuid, limit: int = 9) -> List[EmployeeList]: + query = users.select()\ + .where((users.c.client_id == client_id) & (users.c.id > str(last_id))).order_by(desc(users.c.id)).limit(limit) + result = await database.fetch_all(query=query) employees_list = [] for employee in result: employee = dict(employee) @@ -47,8 +50,9 @@ async def get_clients_db() -> List[ClientDB]: return [ClientDB(**dict(client)) for client in result] -async def get_clients() -> List[ClientShort]: - result = await database.fetch_all(clients.select()) +async def get_clients(last_id: int = 0, limit: int = 9) -> List[ClientShort]: + query = clients.select().where(clients.c.id > last_id).limit(limit) + result = await database.fetch_all(query=query) clients_list = [] for client in result: client = dict(client) @@ -60,8 +64,8 @@ async def get_clients() -> List[ClientShort]: return clients_list -async def get_clients_page() -> ClientsPage: - clients_list = await get_clients() +async def get_clients_page(last_id: int = 0, limit: int = 9) -> ClientsPage: + clients_list = await get_clients(last_id, limit) licences_list = await get_licences_db() return ClientsPage(**dict({"clients_list": clients_list, "licences_list": licences_list})) @@ -71,18 +75,18 @@ async def get_client_info(client_id: int) -> Client: return client -async def get_client_page(client_id: int) -> ClientPage: +async def get_client_page(client_id: int, last_id: UUID4 = default_uuid, limit: int = 9) -> ClientPage: client = await get_client(client_id) - employees_list = await get_employees(client_id) + employees_list = await get_employees(client_id, last_id, limit) licences_list = await get_client_licences(client_id) return ClientPage(**dict({"client": client, "employees_list": employees_list, "licences_list": licences_list})) -async def get_dev_client_page(client_id: int) -> DevClientPage: +async def get_dev_client_page(client_id: int, last_id: UUID4 = default_uuid, limit: int = 9) -> DevClientPage: client = await get_client(client_id) - employees_list = await get_employees(client_id) + employees_list = await get_employees(client_id, last_id, limit) software_list = await get_software_db_list() return DevClientPage(**dict({"client": client, "employees_list": employees_list, diff --git a/src/accounts/developer_account/routers.py b/src/accounts/developer_account/routers.py index d51fcbe..778eb66 100644 --- a/src/accounts/developer_account/routers.py +++ b/src/accounts/developer_account/routers.py @@ -5,7 +5,8 @@ from .services import get_developer, add_developer, delete_developer from src.users.models import UserTable -from src.users.logic import developer_user, get_developers, change_pwd, pre_update_developer, get_email_with_changed_pwd +from src.users.logic import developer_user, get_developers, change_pwd, pre_update_developer, \ + get_email_with_changed_pwd, default_uuid from src.users.schemas import UserDB, UserUpdate, DeveloperList, DeveloperCreate from .statistics.routers import statistics_router @@ -14,8 +15,8 @@ @developer_router.get("/", response_model=List[DeveloperList], status_code=status.HTTP_200_OK) -async def developers_list(user: UserTable = Depends(developer_user)): - return await get_developers() +async def developers_list(user: UserTable = Depends(developer_user), last_id: UUID4 = default_uuid, limit: int = 9): + return await get_developers(last_id, limit) @developer_router.get("/{id:uuid}", response_model=UserDB, status_code=status.HTTP_200_OK) diff --git a/src/accounts/developer_account/statistics/services.py b/src/accounts/developer_account/statistics/services.py index 9842744..232de98 100644 --- a/src/accounts/developer_account/statistics/services.py +++ b/src/accounts/developer_account/statistics/services.py @@ -5,7 +5,7 @@ DevelopersStatistics, DeveloperStatistics, SoftwareStatistics, ModuleStatistics, StatusStatistics, ClientStatistics from src.desk.services import get_appeals_by_developer, get_appeals_by_client, \ get_appeals_by_software, get_appeals_by_module, get_appeals_db, get_status_distribution, get_statuses_list -from src.reference_book.services import get_software_db_list, get_modules +from src.reference_book.services import get_software_db_list, get_modules_db from src.users.logic import get_developers_db @@ -20,7 +20,7 @@ async def get_software_list_stat() -> List[SoftwareStatistics]: async def get_module_list_stat() -> List[ModuleStatistics]: - modules = await get_modules() + modules = await get_modules_db() modules_list = [] for module in modules: appeals = await get_appeals_by_module(module.id) diff --git a/src/accounts/employee_account/routers.py b/src/accounts/employee_account/routers.py index 5a3d968..0a84426 100644 --- a/src/accounts/employee_account/routers.py +++ b/src/accounts/employee_account/routers.py @@ -15,12 +15,6 @@ employee_router = APIRouter() -# @employee_router.get("/{id}/employees", response_model=List[UserDB], status_code=status.HTTP_200_OK) -# async def employees_list(id: int, user: UserTable = Depends(any_user)): -# user = await get_client_users_with_superuser(id, user) -# return await get_employees(id) - - @employee_router.get("/{id}/employees/{pk}", response_model=Optional[EmployeePage], status_code=status.HTTP_200_OK) async def employee(id: int, pk: UUID4, user: UserTable = Depends(any_user)): user = await get_client_users_with_superuser(id, user) diff --git a/src/accounts/employee_account/services.py b/src/accounts/employee_account/services.py index 2e74722..1fc5bcd 100644 --- a/src/accounts/employee_account/services.py +++ b/src/accounts/employee_account/services.py @@ -5,7 +5,7 @@ from pydantic.types import UUID4 from src.accounts.client_account.schemas import EmployeePage -from src.accounts.client_account.services import get_client, get_employees +from src.accounts.client_account.services import get_client, get_count_employees from src.db.db import database from src.reference_book.services import get_client_licences, add_employee_licence from src.service import send_mail, Email @@ -29,8 +29,8 @@ async def get_count_allowed_employees(client_id: int) -> int: client_licences = await get_client_licences(client_id) for licence in client_licences: count_allowed_employees += licence.count_members - client_employees = await get_employees(client_id) - return count_allowed_employees - len(client_employees) + count_employees = await get_count_employees(client_id) + return count_allowed_employees - count_employees async def add_employee(id: int, user: PreEmployeeCreate) -> UserDB: diff --git a/src/desk/routes.py b/src/desk/routes.py index 1e6c2fb..c9683d9 100644 --- a/src/desk/routes.py +++ b/src/desk/routes.py @@ -1,11 +1,11 @@ from fastapi import APIRouter, status, Depends, Response, HTTPException, UploadFile, File -from typing import List, Union, Optional +from typing import List from .services import get_all_appeals, get_appeal, get_comments, get_comment, \ add_appeal, add_comment, update_appeal, update_comment, delete_comment, get_appeals_page, upload_attachment, \ delete_attachment, get_attachment, update_dev_appeal, check_access, get_dev_appeal -from .schemas import Appeal, CommentShort, Comment, AppealCreate, CommentCreate, CommentDB, \ - AppealUpdate, AppealDB, AppealShort, CommentUpdate, DevAppeal, AttachmentDB, AppealList, AppealsPage +from .schemas import CommentShort, Comment, AppealCreate, CommentCreate, CommentDB, \ + AppealUpdate, AppealDB, CommentUpdate, AttachmentDB from ..users.models import UserTable from ..users.logic import employee, any_user @@ -13,10 +13,10 @@ @router.get("/", status_code=status.HTTP_200_OK) -async def appeals_list(user: UserTable = Depends(any_user)): +async def appeals_list(last_id: int = 0, limit: int = 9, user: UserTable = Depends(any_user)): if user.is_superuser: - return await get_all_appeals() - return await get_appeals_page(user) + return await get_all_appeals(last_id, limit) + return await get_appeals_page(user, last_id, limit) @router.get("/{id}", status_code=status.HTTP_200_OK) diff --git a/src/desk/services.py b/src/desk/services.py index ec1a49b..6fa2a54 100644 --- a/src/desk/services.py +++ b/src/desk/services.py @@ -11,8 +11,8 @@ from ..db.db import database from .models import appeals, comments, attachments from ..errors import Errors -from ..reference_book.services import get_software, get_module, get_modules, get_software_db_list -from ..users.logic import get_developers, get_or_404, get_user +from ..reference_book.services import get_software, get_module, get_modules, get_software_db_list, get_modules_db +from ..users.logic import get_or_404, get_user, get_developers_db from ..users.models import UserTable, users from .models import StatusTasks from ..service import check_dict, send_mail, Email @@ -21,8 +21,9 @@ from fastapi.responses import FileResponse -async def get_all_appeals() -> List[AppealList]: - result = await database.fetch_all(query=appeals.select()) +async def get_all_appeals(last_id: int = 0, limit: int = 9) -> List[AppealList]: + query = appeals.select().where(appeals.c.id > last_id).limit(limit) + result = await database.fetch_all(query=query) appeals_list = [] for appeal in result: appeal = dict(appeal) @@ -30,8 +31,8 @@ async def get_all_appeals() -> List[AppealList]: return appeals_list -async def get_appeals(user: UserTable) -> List[AppealList]: - query = appeals.select().where(appeals.c.client_id == user.client_id) +async def get_appeals(user: UserTable, last_id: int = 0, limit: int = 9) -> List[AppealList]: + query = appeals.select().where((appeals.c.client_id == user.client_id) & (appeals.c.id > last_id)).limit(limit) result = await database.fetch_all(query=query) appeals_list = [] for appeal in result: @@ -140,11 +141,11 @@ async def get_appeal_list(appeal: Dict) -> AppealList: "module": module})) -async def get_appeals_page(user: UserTable) -> AppealsPage: - appeals_list = await get_appeals(user) +async def get_appeals_page(user: UserTable, last_id: int = 0, limit: int = 9) -> AppealsPage: + appeals_list = await get_appeals(user, last_id, limit) client = await get_client(user.client_id) - software_list = await get_software_db_list() - modules_list = await get_modules() + software_list = await get_software_db_list() # TODO выдавать софт клиента + modules_list = await get_modules() # TODO выдавать модули клиента return AppealsPage(**dict({"appeals": appeals_list, "client": client, "software_list": software_list, @@ -172,9 +173,9 @@ async def get_appeal(appeal_id: int, user: UserTable) -> Appeal: async def get_dev_appeal(appeal_id: int, user: UserTable, appeal: Appeal) -> DevAppeal: - developers = await get_developers() + developers = await get_developers_db() allowed_statuses = await get_next_status(appeal_id, user) - modules_list = await get_modules() + modules_list = await get_modules_db() software_list = await get_software_db_list() result = DevAppeal(**dict({**dict(appeal), "software_list": software_list, @@ -393,7 +394,7 @@ async def notify_create_appeal(appeal_id: int, user: UserTable) -> None: client = await get_client_db(appeal.client_id) message = f"Представителем закачика «{client.name}» - {user.name} {user.surname} " \ f"было создано обращение №{appeal_id} - «{appeal.topic}»" - developers = await get_developers() + developers = await get_developers_db() for developer in developers: await notify_user(developer.email, message) return None diff --git a/src/reference_book/api/licence.py b/src/reference_book/api/licence.py index fe554f1..bb91810 100644 --- a/src/reference_book/api/licence.py +++ b/src/reference_book/api/licence.py @@ -1,8 +1,6 @@ -from typing import List - from fastapi import APIRouter, Depends, status, Response from ..schemas import Licence, LicenceCreate, LicenceDB, LicenceUpdate, LicencePage -from ..services import get_licence, get_licences, add_licence, update_licence, delete_licence, get_licence_page +from ..services import get_licence, add_licence, update_licence, delete_licence, get_licence_page from ...users.models import UserTable from ...users.logic import developer_user @@ -10,9 +8,9 @@ @router.get("/", response_model=LicencePage, status_code=status.HTTP_200_OK) -async def licence_list(user: UserTable = Depends(developer_user)): +async def licence_list(last_id: int = 0, limit: int = 9, user: UserTable = Depends(developer_user)): if user.is_superuser: - return await get_licence_page() + return await get_licence_page(last_id, limit) return None diff --git a/src/reference_book/api/module.py b/src/reference_book/api/module.py index 92fa40b..209a9c7 100644 --- a/src/reference_book/api/module.py +++ b/src/reference_book/api/module.py @@ -10,8 +10,8 @@ @router.get('/', response_model=List[ModuleDB], status_code=status.HTTP_200_OK) -async def modules_list(user: UserTable = Depends(developer_user)): - return await get_modules() +async def modules_list(last_id: int = 0, limit: int = 9, user: UserTable = Depends(developer_user)): + return await get_modules(last_id, limit) @router.get('/{id}', response_model=ModuleDB, status_code=status.HTTP_200_OK) @@ -24,7 +24,7 @@ async def create_module(item: ModuleCreate, user: UserTable = Depends(developer_ return await add_module(item) -@router.put("/{id}", response_model=ModuleDB, status_code=status.HTTP_201_CREATED) +@router.patch("/{id}", response_model=ModuleDB, status_code=status.HTTP_201_CREATED) async def update_module_by_id(id: int, item: ModuleUpdate, user: UserTable = Depends(developer_user)): return await update_module(id, item) diff --git a/src/reference_book/api/routes.py b/src/reference_book/api/routes.py index 1473269..cd95fdc 100644 --- a/src/reference_book/api/routes.py +++ b/src/reference_book/api/routes.py @@ -10,9 +10,9 @@ @router.get('/', status_code=status.HTTP_200_OK) async def get_reference_book(user: UserTable = Depends(developer_user)): - licences = await licence_list(user) - modules = await modules_list(user) - softwares = await software_list(user) + licences = await licence_list(user=user) + modules = await modules_list(user=user) + softwares = await software_list(user=user) return {"licences": licences, "modules": modules, "softwares": softwares} router.include_router(licence_router, prefix='/licences') diff --git a/src/reference_book/api/software.py b/src/reference_book/api/software.py index ac63ef2..0944432 100644 --- a/src/reference_book/api/software.py +++ b/src/reference_book/api/software.py @@ -10,8 +10,8 @@ @router.get('/', response_model=SoftwarePage, status_code=status.HTTP_200_OK) -async def software_list(user: UserTable = Depends(developer_user)): - return await get_software_page() +async def software_list(last_id: int = 0, limit: int = 9, user: UserTable = Depends(developer_user)): + return await get_software_page(last_id, limit) @router.get('/{id}', response_model=Software, status_code=status.HTTP_200_OK) @@ -24,7 +24,7 @@ async def create_software(new_software: SoftwareWithModulesCreate, user: UserTab return await add_software_with_modules(new_software) -@router.put("/{id}", response_model=SoftwareDB, status_code=status.HTTP_201_CREATED) +@router.patch("/{id}", response_model=SoftwareDB, status_code=status.HTTP_201_CREATED) async def update_software_by_id(id: int, item: SoftwareUpdate, user: UserTable = Depends(developer_user)): return await update_software(id, item) diff --git a/src/reference_book/services.py b/src/reference_book/services.py index 078c845..196b381 100644 --- a/src/reference_book/services.py +++ b/src/reference_book/services.py @@ -12,8 +12,9 @@ from typing import List, Optional -async def get_software_list() -> List[Software]: - result = await database.fetch_all(query=softwares.select()) +async def get_software_list(last_id: int = 0, limit: int = 9) -> List[Software]: + query = softwares.select().where(softwares.c.id > last_id).limit(limit) + result = await database.fetch_all(query=query) list_of_software = [] for software in result: software = dict(software) @@ -27,9 +28,9 @@ async def get_software_db_list() -> List[SoftwareDB]: return [SoftwareDB(**dict(software)) for software in result] -async def get_software_page() -> SoftwarePage: - software_list = await get_software_list() - modules_list = await get_modules() +async def get_software_page(last_id: int = 0, limit: int = 9) -> SoftwarePage: + software_list = await get_software_list(last_id, limit) + modules_list = await get_modules_db() return SoftwarePage(**dict({"software_list": software_list, "modules_list": modules_list})) @@ -113,9 +114,7 @@ async def get_software_module(software_id: int, module_id: int) -> Optional[Soft async def get_software_modules(software_id: int) -> List[ModuleDB]: query = software_modules.select().where(software_modules.c.software_id == software_id) result = await database.fetch_all(query=query) - list = [await get_module(software_module.module_id) for software_module in result] - print(list) - return list # TODO проверить работу метода (software_module.module_id) + return [await get_module(software_module.module_id) for software_module in result] async def add_software_module(software_id: int, module_id: int) -> SoftwareModulesDB: @@ -146,8 +145,14 @@ async def delete_software_module(software_id: int, module_id: int) -> None: await database.execute(query) -async def get_modules() -> List[ModuleDB]: +async def get_modules_db() -> List[ModuleDB]: result = await database.fetch_all(query=modules.select()) + return [ModuleDB(**dict(module)) for module in result] + + +async def get_modules(last_id: int = 0, limit: int = 9) -> List[ModuleDB]: + query = modules.select().where(modules.c.id > last_id).limit(limit) + result = await database.fetch_all(query=query) modules_list = [] for module in result: modules_list.append(ModuleDB(**dict(module))) @@ -211,8 +216,9 @@ async def get_licences_db() -> List[LicenceDB]: return licences_list -async def get_licences() -> List[Licence]: - result = await database.fetch_all(query=licences.select()) +async def get_licences(last_id: int = 0, limit: int = 9) -> List[Licence]: + query = licences.select().where(licences.c.id > last_id).limit(limit) + result = await database.fetch_all(query=query) licences_list = [] for licence in result: licence = dict(licence) @@ -222,8 +228,8 @@ async def get_licences() -> List[Licence]: return licences_list -async def get_licence_page() -> LicencePage: - licences_list = await get_licences() +async def get_licence_page(last_id: int = 0, limit: int = 9) -> LicencePage: + licences_list = await get_licences(last_id, limit) software_list = await get_software_db_list() return LicencePage(**dict({"licences_list": licences_list, "software_list": software_list})) @@ -371,7 +377,7 @@ async def get_client_licences(client_id: int) -> List[Licence]: client_licence = dict(client_licence) licence = dict(await get_licence_db(client_licence["licence_id"])) closed_vacancies = await get_count_employee_for_licence_id(client_licence["licence_id"]) - software = await get_software(licence["software_id"]) # TODO разобраться с None + software = await get_software(licence["software_id"]) client_licences_list.append( Licence(**dict({**licence, "closed_vacancies": closed_vacancies, "software": software}))) return client_licences_list diff --git a/src/users/logic.py b/src/users/logic.py index 39da034..8e7e09d 100644 --- a/src/users/logic.py +++ b/src/users/logic.py @@ -5,6 +5,7 @@ from fastapi_users.authentication import JWTAuthentication from fastapi_users.password import get_password_hash from fastapi_users.router import ErrorCode +from sqlalchemy import desc from ..config import SECRET from .schemas import User, UserCreate, UserUpdate, UserDB, DeveloperList, generate_pwd, EmployeeUpdate, DeveloperCreate @@ -39,6 +40,9 @@ developer_user = all_users.current_user(active=True, superuser=True) +default_uuid = UUID4("00000000-0000-0000-0000-000000000000") + + async def get_owner(client_id: int, user: UserTable = Depends(any_user)): if not (user.client_id == client_id and user.is_owner): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) @@ -98,8 +102,9 @@ async def get_developers_db() -> List[UserDB]: return [UserDB(**dict(developer)) for developer in result] -async def get_developers() -> List[DeveloperList]: - query = users.select().where(users.c.is_superuser == 1) +async def get_developers(last_id: UUID4 = default_uuid, limit: int = 9) -> List[DeveloperList]: + query = users.select()\ + .where((users.c.is_superuser == 1) & (users.c.id > str(last_id))).order_by(desc(users.c.id)).limit(limit) result = await database.fetch_all(query=query) developers = [] for developer in result: diff --git a/src/users/routers.py b/src/users/routers.py index 07e68d2..4dd5cac 100644 --- a/src/users/routers.py +++ b/src/users/routers.py @@ -9,7 +9,7 @@ from ..config import SECRET from src.users.logic import jwt_authentication, all_users, \ - on_after_forgot_password, on_after_reset_password, after_verification, after_verification_request, any_user, \ + after_verification, after_verification_request, any_user, \ get_new_password, create_developer router = APIRouter() @@ -52,26 +52,10 @@ async def login( return {"token": token, "user": user} -# router.include_router( -# all_users.get_auth_router(jwt_authentication), -# prefix="/auth/jwt", -# tags=["auth"]) router.include_router( all_users.get_register_router(), prefix="/auth", tags=["auth"]) -# router.include_router( -# all_users.get_reset_password_router( -# SECRET, -# after_forgot_password=on_after_forgot_password, -# after_reset_password=on_after_reset_password), -# prefix="/auth", -# tags=["auth"]) -# router.include_router( -# all_users.get_users_router(), -# prefix="/users", -# tags=["users"]) - router.include_router( all_users.get_verify_router( SECRET,