Skip to content

Add Items (crud, models, endpoints), utils, refactor #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@ After using this generator, your new project (the directory created) will contai

### Next release

* PR <a href="https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/14" target="_blank">#14</a>:
* Update CRUD utils to use types better.
* Simplify Pydantic model names, from `UserInCreate` to `UserCreate`, etc.
* Upgrade packages.
* Add new generic "Items" models, crud utils, endpoints, and tests. To facilitate re-using them to create new functionality. As they are simple and generic (not like Users), it's easier to copy-paste and adapt them to each use case.
* Update endpoints/*path operations* to simplify code and use new utilities, prefix and tags in `include_router`.
* Update testing utils.
* Update linting rules, relax vulture to reduce false positives.
* Update migrations to include new Items.
* Update project README.md with tips about how to start with backend.

* Upgrade Python to 3.7 as Celery is now compatible too. <a href="https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/10" target="_blank">PR #10</a> by <a href="https://github.com/ebreton" target="_blank">@ebreton</a>.

### 0.2.2
Expand Down
4 changes: 3 additions & 1 deletion {{cookiecutter.project_slug}}/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ If your Docker is not running in `localhost` (the URLs above wouldn't work) chec

### General workflow

Add and modify SQLAlchemy models in `./backend/app/app/db_models/`, Pydantic models in `./backend/app/app/models` and API endpoints in `./backend/app/app/api/`.
Open your editor at `./backend/app/` (instead of the project root: `./`), so that you see an `./app/` directory with your code inside. That way, your editor will be able to find all the imports, etc.

Modify or add SQLAlchemy models in `./backend/app/app/db_models/`, Pydantic models in `./backend/app/app/models/`, API endpoints in `./backend/app/app/api/`, CRUD (Create, Read, Update, Delete) utils in `./backend/app/app/crud/`. The easiest might be to copy the ones for Items (models, endpoints, and CRUD utils) and update them to your needs.

Add and modify tasks to the Celery worker in `./backend/app/app/worker.py`.

Expand Down
2 changes: 1 addition & 1 deletion {{cookiecutter.project_slug}}/backend/app/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pyjwt = "*"
python-multipart = "*"
email-validator = "*"
requests = "*"
celery = "~=4.3"
celery = "*"
passlib = {extras = ["bcrypt"],version = "*"}
tenacity = "*"
pydantic = "*"
Expand Down
1,022 changes: 0 additions & 1,022 deletions {{cookiecutter.project_slug}}/backend/app/Pipfile.lock

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
"""First revision

Revision ID: e6ae69e9dcb9
Revision ID: d4867f3a4c0a
Revises:
Create Date: 2019-02-13 14:27:57.038583
Create Date: 2019-04-17 13:53:32.978401

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'e6ae69e9dcb9'
revision = 'd4867f3a4c0a'
down_revision = None
branch_labels = None
depends_on = None
Expand All @@ -30,11 +30,26 @@ def upgrade():
op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True)
op.create_index(op.f('ix_user_full_name'), 'user', ['full_name'], unique=False)
op.create_index(op.f('ix_user_id'), 'user', ['id'], unique=False)
op.create_table('item',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(), nullable=True),
sa.Column('description', sa.String(), nullable=True),
sa.Column('owner_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_item_description'), 'item', ['description'], unique=False)
op.create_index(op.f('ix_item_id'), 'item', ['id'], unique=False)
op.create_index(op.f('ix_item_title'), 'item', ['title'], unique=False)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_item_title'), table_name='item')
op.drop_index(op.f('ix_item_id'), table_name='item')
op.drop_index(op.f('ix_item_description'), table_name='item')
op.drop_table('item')
op.drop_index(op.f('ix_user_id'), table_name='user')
op.drop_index(op.f('ix_user_full_name'), table_name='user')
op.drop_index(op.f('ix_user_email'), table_name='user')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from fastapi import APIRouter

from app.api.api_v1.endpoints import token, user, utils
from app.api.api_v1.endpoints import items, login, users, utils

api_router = APIRouter()
api_router.include_router(token.router)
api_router.include_router(user.router)
api_router.include_router(utils.router)
api_router.include_router(login.router, tags=["login"])
api_router.include_router(users.router, prefix="/users", tags=["users"])
api_router.include_router(utils.router, prefix="/utils", tags=["utils"])
api_router.include_router(items.router, prefix="/items", tags=["items"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from typing import List

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session

from app import crud
from app.api.utils.db import get_db
from app.api.utils.security import get_current_active_user
from app.db_models.user import User as DBUser
from app.models.item import Item, ItemCreate, ItemUpdate

router = APIRouter()


@router.get("/", response_model=List[Item])
def read_items(
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100,
current_user: DBUser = Depends(get_current_active_user),
):
"""
Retrieve items.
"""
if crud.user.is_superuser(current_user):
items = crud.item.get_multi(db, skip=skip, limit=limit)
else:
items = crud.item.get_multi_by_owner(
db_session=db, owner_id=current_user.id, skip=skip, limit=limit
)
return items


@router.post("/", response_model=Item)
def create_item(
*,
db: Session = Depends(get_db),
item_in: ItemCreate,
current_user: DBUser = Depends(get_current_active_user),
):
"""
Create new item.
"""
item = crud.item.create(db_session=db, item_in=item_in, owner_id=current_user.id)
return item


@router.put("/{id}", response_model=Item)
def update_item(
*,
db: Session = Depends(get_db),
id: int,
item_in: ItemUpdate,
current_user: DBUser = Depends(get_current_active_user),
):
"""
Update an item.
"""
item = crud.item.get(db_session=db, id=id)
if not item:
raise HTTPException(status_code=404, detail="Item not found")
if not crud.user.is_superuser(current_user) and (item.owner_id != current_user.id):
raise HTTPException(status_code=400, detail="Not enough permissions")
item = crud.item.update(db_session=db, item=item, item_in=item_in)
return item


@router.get("/{id}", response_model=Item)
def read_user_me(
*,
db: Session = Depends(get_db),
id: int,
current_user: DBUser = Depends(get_current_active_user),
):
"""
Get item by ID.
"""
item = crud.item.get(db_session=db, id=id)
if not item:
raise HTTPException(status_code=400, detail="Item not found")
if not crud.user.is_superuser(current_user) and (item.owner_id != current_user.id):
raise HTTPException(status_code=400, detail="Not enough permissions")
return item


@router.delete("/{id}", response_model=Item)
def delete_item(
*,
db: Session = Depends(get_db),
id: int,
current_user: DBUser = Depends(get_current_active_user),
):
"""
Delete an item.
"""
item = crud.item.get(db_session=db, id=id)
if not item:
raise HTTPException(status_code=404, detail="Item not found")
if not crud.user.is_superuser(current_user) and (item.owner_id != current_user.id):
raise HTTPException(status_code=400, detail="Not enough permissions")
item = crud.item.remove(db_session=db, id=id)
return item
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,35 @@
from app.api.utils.security import get_current_active_superuser, get_current_active_user
from app.core import config
from app.db_models.user import User as DBUser
from app.models.user import User, UserInCreate, UserInDB, UserInUpdate
from app.models.user import User, UserCreate, UserInDB, UserUpdate
from app.utils import send_new_account_email

router = APIRouter()


@router.get("/users/", tags=["users"], response_model=List[User])
@router.get("/", response_model=List[User])
def read_users(
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100,
current_user: DBUser = Depends(get_current_active_superuser),
):
"""
Retrieve users
Retrieve users.
"""
users = crud.user.get_multi(db, skip=skip, limit=limit)
return users


@router.post("/users/", tags=["users"], response_model=User)
@router.post("/", response_model=User)
def create_user(
*,
db: Session = Depends(get_db),
user_in: UserInCreate,
user_in: UserCreate,
current_user: DBUser = Depends(get_current_active_superuser),
):
"""
Create new user
Create new user.
"""
user = crud.user.get_by_email(db, email=user_in.email)
if user:
Expand All @@ -54,7 +54,7 @@ def create_user(
return user


@router.put("/users/me", tags=["users"], response_model=User)
@router.put("/me", response_model=User)
def update_user_me(
*,
db: Session = Depends(get_db),
Expand All @@ -64,10 +64,10 @@ def update_user_me(
current_user: DBUser = Depends(get_current_active_user),
):
"""
Update own user
Update own user.
"""
current_user_data = jsonable_encoder(current_user)
user_in = UserInUpdate(**current_user_data)
user_in = UserUpdate(**current_user_data)
if password is not None:
user_in.password = password
if full_name is not None:
Expand All @@ -78,18 +78,18 @@ def update_user_me(
return user


@router.get("/users/me", tags=["users"], response_model=User)
@router.get("/me", response_model=User)
def read_user_me(
db: Session = Depends(get_db),
current_user: DBUser = Depends(get_current_active_user),
):
"""
Get current user
Get current user.
"""
return current_user


@router.post("/users/open", tags=["users"], response_model=User)
@router.post("/open", response_model=User)
def create_user_open(
*,
db: Session = Depends(get_db),
Expand All @@ -98,7 +98,7 @@ def create_user_open(
full_name: str = Body(None),
):
"""
Create new user without the need to be logged in
Create new user without the need to be logged in.
"""
if not config.USERS_OPEN_REGISTRATION:
raise HTTPException(
Expand All @@ -111,19 +111,19 @@ def create_user_open(
status_code=400,
detail="The user with this username already exists in the system",
)
user_in = UserInCreate(password=password, email=email, full_name=full_name)
user_in = UserCreate(password=password, email=email, full_name=full_name)
user = crud.user.create(db, user_in=user_in)
return user


@router.get("/users/{user_id}", tags=["users"], response_model=User)
@router.get("/{user_id}", response_model=User)
def read_user_by_id(
user_id: int,
current_user: DBUser = Depends(get_current_active_user),
db: Session = Depends(get_db),
):
"""
Get a specific user by id
Get a specific user by id.
"""
user = crud.user.get(db, user_id=user_id)
if user == current_user:
Expand All @@ -135,19 +135,18 @@ def read_user_by_id(
return user


@router.put("/users/{user_id}", tags=["users"], response_model=User)
@router.put("/{user_id}", response_model=User)
def update_user(
*,
db: Session = Depends(get_db),
user_id: int,
user_in: UserInUpdate,
user_in: UserUpdate,
current_user: UserInDB = Depends(get_current_active_superuser),
):
"""
Update a user
Update a user.
"""
user = crud.user.get(db, user_id=user_id)

if not user:
raise HTTPException(
status_code=404,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@
router = APIRouter()


@router.post("/test-celery/", tags=["utils"], response_model=Msg, status_code=201)
@router.post("/test-celery/", response_model=Msg, status_code=201)
def test_celery(
msg: Msg, current_user: UserInDB = Depends(get_current_active_superuser)
):
"""
Test Celery worker
Test Celery worker.
"""
celery_app.send_task("app.worker.test_celery", args=[msg.msg])
return {"msg": "Word received"}


@router.post("/test-email/", tags=["utils"], response_model=Msg, status_code=201)
@router.post("/test-email/", response_model=Msg, status_code=201)
def test_email(
email_to: EmailStr, current_user: UserInDB = Depends(get_current_active_superuser)
):
"""
Test emails
Test emails.
"""
send_test_email(email_to=email_to)
return {"msg": "Test email sent"}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from . import user
from . import item, user
Loading