Skip to content

Commit c3318ae

Browse files
authored
Merge pull request #11 from Wudext/Adding-#10
Adding #10 and #6
2 parents 11a9478 + dca9171 commit c3318ae

13 files changed

Lines changed: 400 additions & 140 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,4 @@ venv.bak/
126126
dmypy.json
127127

128128
# Pyre type checker
129-
.pyre/
129+
.pyre/

migrations/versions/670f4caac7dd_init.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,24 @@
88

99

1010
def upgrade():
11-
op.create_table('category',
12-
sa.Column('id', sa.Integer(), nullable=False),
13-
sa.Column('name', sa.String(), nullable=True),
14-
sa.Column('type', sa.String(), nullable=True),
15-
sa.PrimaryKeyConstraint('id')
11+
op.create_table(
12+
'category',
13+
sa.Column('id', sa.Integer(), nullable=False),
14+
sa.Column('name', sa.String(), nullable=True),
15+
sa.Column('type', sa.String(), nullable=True),
16+
sa.PrimaryKeyConstraint('id'),
1617
)
17-
op.create_table('button',
18-
sa.Column('id', sa.Integer(), nullable=False),
19-
sa.Column('name', sa.String(), nullable=True),
20-
sa.Column('category_id', sa.Integer(), nullable=True),
21-
sa.Column('icon', sa.String(), nullable=True),
22-
sa.ForeignKeyConstraint(['category_id'], ['category.id'], ),
23-
sa.PrimaryKeyConstraint('id')
18+
op.create_table(
19+
'button',
20+
sa.Column('id', sa.Integer(), nullable=False),
21+
sa.Column('name', sa.String(), nullable=True),
22+
sa.Column('category_id', sa.Integer(), nullable=True),
23+
sa.Column('icon', sa.String(), nullable=True),
24+
sa.ForeignKeyConstraint(
25+
['category_id'],
26+
['category.id'],
27+
),
28+
sa.PrimaryKeyConstraint('id'),
2429
)
2530

2631

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""order
2+
3+
Revision ID: 6a486347af93
4+
Revises: 670f4caac7dd
5+
Create Date: 2023-02-11 10:18:11.179485
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '6a486347af93'
14+
down_revision = '670f4caac7dd'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
op.add_column('button', sa.Column('order', sa.Integer(), nullable=False))
21+
op.add_column('button', sa.Column('link', sa.String(), nullable=False))
22+
op.add_column('button', sa.Column('type', sa.String(), nullable=False))
23+
op.alter_column('button', 'name', existing_type=sa.VARCHAR(), nullable=False)
24+
op.alter_column('button', 'category_id', existing_type=sa.INTEGER(), nullable=False)
25+
op.alter_column('button', 'icon', existing_type=sa.VARCHAR(), nullable=False)
26+
op.add_column('category', sa.Column('order', sa.Integer(), nullable=False))
27+
op.alter_column('category', 'name', existing_type=sa.VARCHAR(), nullable=False)
28+
op.alter_column('category', 'type', existing_type=sa.VARCHAR(), nullable=False)
29+
30+
31+
def downgrade():
32+
# ### commands auto generated by Alembic - please adjust! ###
33+
op.alter_column('category', 'type', existing_type=sa.VARCHAR(), nullable=True)
34+
op.alter_column('category', 'name', existing_type=sa.VARCHAR(), nullable=True)
35+
op.drop_column('category', 'order')
36+
op.alter_column('button', 'icon', existing_type=sa.VARCHAR(), nullable=True)
37+
op.alter_column('button', 'category_id', existing_type=sa.INTEGER(), nullable=True)
38+
op.alter_column('button', 'name', existing_type=sa.VARCHAR(), nullable=True)
39+
op.drop_column('button', 'type')
40+
op.drop_column('button', 'link')
41+
op.drop_column('button', 'order')
42+
# ### end Alembic commands ###
Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1-
from sqlalchemy import Column, Integer, String, ForeignKey
2-
from sqlalchemy.orm import relationship
1+
from __future__ import annotations
2+
from sqlalchemy import Integer, String, ForeignKey
3+
from sqlalchemy.orm import relationship, Mapped, mapped_column
34
from .base import Base
45

56

67
class Category(Base):
7-
id = Column(Integer, primary_key=True)
8-
name = Column(String)
9-
type = Column(String)
10-
buttons = relationship("Button", back_populates="category", foreign_keys="Button.category_id")
8+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
9+
order: Mapped[int] = mapped_column(Integer, default=1)
10+
name: Mapped[str] = mapped_column(String)
11+
type: Mapped[str] = mapped_column(String)
12+
buttons: Mapped[list[Button]] = relationship("Button", back_populates="category", foreign_keys="Button.category_id")
1113

1214

1315
class Button(Base):
14-
id = Column(Integer, primary_key=True)
15-
name = Column(String)
16-
category_id = Column(Integer, ForeignKey(Category.id))
17-
category = relationship("Category", back_populates="buttons", foreign_keys=[category_id])
18-
icon = Column(String)
16+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
17+
name: Mapped[str] = mapped_column(String)
18+
order: Mapped[int] = mapped_column(Integer, default=1)
19+
category_id: Mapped[int] = mapped_column(Integer, ForeignKey(Category.id))
20+
category: Mapped[Category] = relationship("Category", back_populates="buttons", foreign_keys=[category_id])
21+
icon: Mapped[str] = mapped_column(String)
22+
link: Mapped[str] = mapped_column(String)
23+
type: Mapped[str] = mapped_column(String)

services_backend/routes/base.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
app = FastAPI()
1111

1212

13-
app.add_middleware(
14-
DBSessionMiddleware, db_url=settings.DB_DSN, session_args={"autocommit": True}, engine_args={"pool_pre_ping": True}
15-
)
13+
app.add_middleware(DBSessionMiddleware, db_url=settings.DB_DSN, engine_args={"pool_pre_ping": True})
1614

1715
app.add_middleware(
1816
CORSMiddleware,
@@ -22,5 +20,5 @@
2220
allow_headers=settings.CORS_ALLOW_HEADERS,
2321
)
2422

25-
app.include_router(button, prefix='/button', tags=["Button"])
23+
app.include_router(button, prefix='/category/{category_id}/button', tags=["Button"])
2624
app.include_router(category, prefix='/category', tags=["Category"])

services_backend/routes/button.py

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,54 +2,94 @@
22
from fastapi_sqlalchemy import db
33

44
from .models.button import ButtonCreate, ButtonUpdate, ButtonGet
5+
from .models.category import CategoryGet
56
from ..models.database import Button, Category
67

78
button = APIRouter()
89

910

1011
@button.post("/", response_model=ButtonGet)
11-
def create_button(button_inp: ButtonCreate):
12-
category = db.session.query(Category).filter(Category.id == button_inp.category_id).one_or_none()
12+
def create_button(button_inp: ButtonCreate, category_id: int):
13+
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
1314
if not category:
1415
raise HTTPException(status_code=404, detail="Category does not exist")
15-
button = Button(**button_inp.dict())
16+
last_button = db.session.query(Button).order_by(Button.order.desc()).first()
17+
button = Button(**button_inp.dict(exclude_none=True))
18+
button.category_id = category_id
19+
if last_button:
20+
button.order = last_button.order + 1
1621
db.session.add(button)
17-
db.session.flush()
22+
db.session.commit()
1823
return button
1924

2025

21-
@button.get("/", response_model=list[ButtonGet])
22-
def get_buttons(offset: int = 0, limit: int = 100):
23-
return db.session.query(Button).offset(offset).limit(limit).all()
26+
@button.get("/", response_model=CategoryGet)
27+
def get_buttons(category_id: int):
28+
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
29+
if not category:
30+
raise HTTPException(status_code=404, detail="Category does not exist")
31+
return category
2432

2533

2634
@button.get("/{button_id}", response_model=ButtonGet)
27-
def get_button(button_id: int):
35+
def get_button(button_id: int, category_id: int):
36+
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
37+
if not category:
38+
raise HTTPException(status_code=404, detail="Category does not exist")
2839
button = db.session.query(Button).filter(Button.id == button_id).one_or_none()
2940
if not button:
3041
raise HTTPException(status_code=404, detail="Button does not exist")
42+
if button.category_id != category_id:
43+
raise HTTPException(status_code=404, detail="Button is not this category")
3144
return button
3245

3346

3447
@button.delete("/{button_id}", response_model=None)
35-
def remove_button(button_id: int):
48+
def remove_button(button_id: int, category_id: int):
49+
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
50+
if not category:
51+
raise HTTPException(status_code=404, detail="Category does not exist")
3652
button = db.session.query(Button).filter(Button.id == button_id).one_or_none()
3753
if not button:
3854
raise HTTPException(status_code=404, detail="Button does not exist")
55+
if button.category_id != category_id:
56+
raise HTTPException(status_code=404, detail="Button is not in this category")
3957
db.session.delete(button)
40-
db.session.flush()
58+
db.session.query(Button).filter(Button.order > button.order).update({"order": Button.order - 1})
59+
db.session.commit()
4160

4261

43-
@button.patch("/{button_id}", response_model=ButtonGet)
44-
def update_button(button_inp: ButtonUpdate, button_id: int):
45-
button = db.session.query(Button).filter(Button.id == button_id)
46-
if not button.one_or_none():
62+
@button.patch("/{button_id}", response_model=ButtonUpdate)
63+
def update_button(button_inp: ButtonUpdate, button_id: int, category_id: int):
64+
query = db.session.query(Button).filter(Button.id == button_id)
65+
button = query.one_or_none()
66+
last_button = (
67+
db.session.query(Button).filter(Button.category_id == category_id).order_by(Button.order.desc()).first()
68+
)
69+
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
70+
71+
if not category:
72+
raise HTTPException(status_code=404, detail="Category does not exist")
73+
if not button:
4774
raise HTTPException(status_code=404, detail="Button does not exist")
4875
if not any(button_inp.dict().values()):
4976
raise HTTPException(status_code=400, detail="Empty schema")
50-
button.update(
51-
button_inp.dict(exclude_unset=True)
52-
)
53-
db.session.flush()
54-
patched = button.one()
55-
return patched
77+
if button.category_id != category_id:
78+
raise HTTPException(status_code=404, detail="Button is not this category")
79+
80+
if button_inp.order:
81+
if last_button and (button_inp.order > last_button.order + 1):
82+
raise HTTPException(
83+
status_code=400,
84+
detail=f"Can`t create button with order {button_inp.order}. " f"Last category is {last_button.order}",
85+
)
86+
if button_inp.order < 1:
87+
raise HTTPException(status_code=400, detail="Order can`t be less than 1")
88+
if button.order > button_inp.order:
89+
db.session.query(Button).filter(Button.order < button.order).update({"order": Button.order + 1})
90+
elif button.order < button_inp.order:
91+
db.session.query(Button).filter(Button.order > button.order).update({"order": Button.order - 1})
92+
93+
query.update(button_inp.dict(exclude_unset=True, exclude_none=True))
94+
db.session.commit()
95+
return button

services_backend/routes/category.py

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,48 +9,77 @@
99

1010
@category.post("/", response_model=CategoryGet)
1111
def create_category(category_inp: CategoryCreate):
12-
category = Category(**category_inp.dict())
12+
last_category = db.session.query(Category).order_by(Category.order.desc()).first()
13+
category = Category(**category_inp.dict(exclude_none=True))
14+
if last_category:
15+
category.order = last_category.order + 1
1316
db.session.add(category)
14-
db.session.flush()
17+
db.session.commit()
1518
return category
1619

1720

18-
@category.get("/", response_model=list[CategoryGet])
21+
@category.get("/", response_model=list[CategoryGet], response_model_exclude_none=True)
1922
def get_categories(offset: int = 0, limit: int = 100):
20-
return db.session.query(Category).offset(offset).limit(limit).all()
23+
if (offset < 0) or (limit < 0):
24+
raise HTTPException(400, detail="Offset or limit cant be negative")
25+
return [
26+
CategoryGet.from_orm(row).dict(exclude={"buttons"})
27+
for row in db.session.query(Category).order_by(Category.order).offset(offset).limit(limit).all()
28+
]
2129

2230

23-
@category.get("/{category_id}", response_model=CategoryGet)
31+
@category.get("/{category_id}", response_model=CategoryGet, response_model_exclude_none=True)
2432
def get_category(category_id: int):
2533
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
2634
if not category:
2735
raise HTTPException(status_code=404, detail="Category does not exist")
28-
return category
36+
return {
37+
"id": category_id,
38+
"order": category.order,
39+
"name": category.name,
40+
"type": category.type,
41+
}
2942

3043

3144
@category.delete("/{category_id}", response_model=None)
3245
def remove_category(category_id: int):
3346
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
34-
if category is None:
47+
if not category:
3548
raise HTTPException(status_code=404, detail="Category does not exist")
36-
delete = db.session.query(Category).filter(Category.id == category_id).one_or_none()
3749
for button in db.session.query(Button).filter(Button.category_id == category_id).all():
3850
db.session.delete(button)
3951
db.session.flush()
40-
db.session.delete(delete)
41-
db.session.flush()
52+
db.session.query(Category).filter(Category.order > category.order).update({"order": Category.order - 1})
53+
db.session.delete(category)
54+
db.session.commit()
4255

4356

4457
@category.patch("/{category_id}", response_model=CategoryUpdate)
4558
def update_category(category_inp: CategoryUpdate, category_id: int):
46-
category = db.session.query(Category).filter(Category.id == category_id)
47-
if not category.one_or_none():
59+
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
60+
last_category = db.session.query(Category).order_by(Category.order.desc()).first()
61+
62+
if not category:
4863
raise HTTPException(status_code=404, detail="Category does not exist")
4964
if not any(category_inp.dict().values()):
5065
raise HTTPException(status_code=400, detail="Empty schema")
51-
category.update(
52-
category_inp.dict(exclude_unset=True)
53-
)
54-
db.session.flush()
55-
patched = category.one()
56-
return patched
66+
67+
if category_inp.order:
68+
if category_inp.order < 1:
69+
raise HTTPException(status_code=400, detail="Order can`t be less than 1")
70+
if last_category and (category_inp.order > last_category.order):
71+
raise HTTPException(
72+
status_code=400,
73+
detail=f"Can`t create category with order {category_inp.order}. "
74+
f"Last category is {last_category.order}",
75+
)
76+
77+
if category.order > category_inp.order:
78+
db.session.query(Category).filter(Category.order < category.order).update({"order": Category.order + 1})
79+
elif category.order < category_inp.order:
80+
db.session.query(Category).filter(Category.order > category.order).update({"order": Category.order - 1})
81+
82+
query = db.session.query(Category).filter(Category.id == category_id)
83+
query.update(category_inp.dict(exclude_unset=True, exclude_none=True))
84+
db.session.commit()
85+
return category

services_backend/routes/models/button.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@
22

33

44
class ButtonCreate(Base):
5-
category_id: int
65
icon: str | None
76
name: str | None
7+
link: str | None
8+
type: str | None
89

910

1011
class ButtonUpdate(Base):
1112
category_id: int | None
1213
icon: str | None
1314
name: str | None
15+
order: int | None
16+
link: str | None
17+
type: str | None
1418

1519

1620
class ButtonGet(Base):
1721
id: int
18-
category_id: int
22+
order: int
1923
icon: str | None
2024
name: str | None
25+
link: str | None
26+
type: str | None

services_backend/routes/models/category.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ class CategoryCreate(Base):
88

99

1010
class CategoryUpdate(Base):
11+
order: int | None
1112
type: str | None
1213
name: str | None
1314

1415

1516
class CategoryGet(Base):
1617
id: int
18+
order: int
1719
type: str | None
1820
name: str | None
19-
buttons: list[ButtonGet]
21+
buttons: list[ButtonGet] | None

0 commit comments

Comments
 (0)