Skip to content
This repository has been archived by the owner on Nov 14, 2022. It is now read-only.

Commit

Permalink
✨ Add Items (crud, models, endpoints), utils, refactor (#15)
Browse files Browse the repository at this point in the history
* 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.
* Add full text search for items.
* Update project README.md with tips about how to start with backend.
  • Loading branch information
tiangolo authored Apr 19, 2019
1 parent aa801a0 commit 87ccdc7
Show file tree
Hide file tree
Showing 32 changed files with 801 additions and 126 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ After using this generator, your new project (the directory created) will contai

## Release Notes

* PR <a href="https://github.com/tiangolo/full-stack-fastapi-couchbase/pull/15" target="_blank">#15</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.
* Add full text search for items.
* Update project README.md with tips about how to start with backend.

### 0.2.1

* Fix frontend hijacking /docs in development. Using latest https://github.com/tiangolo/node-frontend with custom Nginx configs in frontend. <a href="https://github.com/tiangolo/full-stack-fastapi-couchbase/pull/14" target="_blank">PR #14</a>.
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

Modify or add 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 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.2.1"
celery = "~=4.3"
passlib = {extras = ["bcrypt"],version = "*"}
tenacity = "*"
pydantic = "*"
Expand Down
11 changes: 6 additions & 5 deletions {{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from fastapi import APIRouter

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

api_router = APIRouter()
api_router.include_router(role.router)
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(roles.router, prefix="/roles", tags=["roles"])
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,134 @@
from typing import List

from fastapi import APIRouter, Depends, HTTPException

from app import crud
from app.api.utils.security import get_current_active_user
from app.db.database import get_default_bucket
from app.models.item import Item, ItemCreate, ItemUpdate
from app.models.user import UserInDB

router = APIRouter()


@router.get("/", response_model=List[Item])
def read_items(
skip: int = 0,
limit: int = 100,
current_user: UserInDB = Depends(get_current_active_user),
):
"""
Retrieve items.
If superuser, all the items.
If normal user, the items owned by this user.
"""
bucket = get_default_bucket()
if crud.user.is_superuser(current_user):
docs = crud.item.get_multi(bucket, skip=skip, limit=limit)
else:
docs = crud.item.get_multi_by_owner(
bucket=bucket, owner_username=current_user.username, skip=skip, limit=limit
)
return docs


@router.get("/search/", response_model=List[Item])
def search_items(
q: str,
skip: int = 0,
limit: int = 100,
current_user: UserInDB = Depends(get_current_active_user),
):
"""
Search items, use Bleve Query String syntax:
http://blevesearch.com/docs/Query-String-Query/
For typeahead suffix with `*`. For example, a query with: `title:foo*` will match
items containing `football`, `fool proof`, etc.
"""
bucket = get_default_bucket()
if crud.user.is_superuser(current_user):
docs = crud.item.search(bucket=bucket, query_string=q, skip=skip, limit=limit)
else:
docs = crud.item.search_with_owner(
bucket=bucket,
query_string=q,
username=current_user.username,
skip=skip,
limit=limit,
)
return docs


@router.post("/", response_model=Item)
def create_item(
*, item_in: ItemCreate, current_user: UserInDB = Depends(get_current_active_user)
):
"""
Create new item.
"""
bucket = get_default_bucket()
id = crud.utils.generate_new_id()
doc = crud.item.upsert(
bucket=bucket, id=id, doc_in=item_in, owner_username=current_user.username
)
return doc


@router.put("/{id}", response_model=Item)
def update_item(
*,
id: str,
item_in: ItemUpdate,
current_user: UserInDB = Depends(get_current_active_user),
):
"""
Update an item.
"""
bucket = get_default_bucket()
doc = crud.item.get(bucket=bucket, id=id)
if not doc:
raise HTTPException(status_code=404, detail="Item not found")
if not crud.user.is_superuser(current_user) and (
doc.owner_username != current_user.username
):
raise HTTPException(status_code=400, detail="Not enough permissions")
doc = crud.item.update(
bucket=bucket, id=id, doc_in=item_in, owner_username=doc.owner_username
)
return doc


@router.get("/{id}", response_model=Item)
def read_item(id: str, current_user: UserInDB = Depends(get_current_active_user)):
"""
Get item by ID.
"""
bucket = get_default_bucket()
doc = crud.item.get(bucket=bucket, id=id)
if not doc:
raise HTTPException(status_code=404, detail="Item not found")
if not crud.user.is_superuser(current_user) and (
doc.owner_username != current_user.username
):
raise HTTPException(status_code=400, detail="Not enough permissions")
return doc


@router.delete("/{id}", response_model=Item)
def delete_item(id: str, current_user: UserInDB = Depends(get_current_active_user)):
"""
Delete an item by ID.
"""
bucket = get_default_bucket()
doc = crud.item.get(bucket=bucket, id=id)
if not doc:
raise HTTPException(status_code=404, detail="Item not found")
if not crud.user.is_superuser(current_user) and (
doc.owner_username != current_user.username
):
raise HTTPException(status_code=400, detail="Not enough permissions")
doc = crud.item.remove(bucket=bucket, id=id)
return doc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from app.db.database import get_default_bucket
from app.models.msg import Msg
from app.models.token import Token
from app.models.user import User, UserInDB, UserInUpdate
from app.models.user import User, UserInDB, UserUpdate
from app.utils import (
generate_password_reset_token,
send_reset_password_email,
Expand All @@ -20,10 +20,10 @@
router = APIRouter()


@router.post("/login/access-token", response_model=Token, tags=["login"])
@router.post("/login/access-token", response_model=Token)
def login(form_data: OAuth2PasswordRequestForm = Depends()):
"""
OAuth2 compatible token login, get an access token for future requests
OAuth2 compatible token login, get an access token for future requests.
"""
bucket = get_default_bucket()
user = crud.user.authenticate(
Expand All @@ -42,18 +42,18 @@ def login(form_data: OAuth2PasswordRequestForm = Depends()):
}


@router.post("/login/test-token", tags=["login"], response_model=User)
@router.post("/login/test-token", response_model=User)
def test_token(current_user: UserInDB = Depends(get_current_user)):
"""
Test access token
Test access token.
"""
return current_user


@router.post("/password-recovery/{username}", tags=["login"], response_model=Msg)
@router.post("/password-recovery/{username}", response_model=Msg)
def recover_password(username: str):
"""
Password Recovery
Password Recovery.
"""
bucket = get_default_bucket()
user = crud.user.get(bucket, username=username)
Expand All @@ -70,10 +70,10 @@ def recover_password(username: str):
return {"msg": "Password recovery email sent"}


@router.post("/reset-password/", tags=["login"], response_model=Msg)
@router.post("/reset-password/", response_model=Msg)
def reset_password(token: str, new_password: str):
"""
Reset password
Reset password.
"""
username = verify_password_reset_token(token)
if not username:
Expand All @@ -87,6 +87,6 @@ def reset_password(token: str, new_password: str):
)
elif not crud.user.is_active(user):
raise HTTPException(status_code=400, detail="Inactive user")
user_in = UserInUpdate(name=username, password=new_password)
user_in = UserUpdate(name=username, password=new_password)
user = crud.user.update(bucket, username=username, user_in=user_in)
return {"msg": "Password updated successfully"}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
router = APIRouter()


@router.get("/roles/", response_model=Roles)
@router.get("/", response_model=Roles)
def read_roles(current_user: UserInDB = Depends(get_current_active_superuser)):
"""
Retrieve roles
Retrieve roles.
"""
roles = crud.utils.ensure_enums_to_strs(RoleEnum)
return {"roles": roles}
Loading

0 comments on commit 87ccdc7

Please sign in to comment.