DEV Community

Cover image for FastAPI with Django ORM and Admin
kathmandu
kathmandu

Posted on • Originally published at qiita.com

FastAPI with Django ORM and Admin

Introduction 😉

I'm Manato Kato, Japanese college student, and I love Python.
I'd like to introduce my new project "FastAPI with Django ORM and Admin".

Beginning 📍

Programming is a means to an end

The reason why I do programming is because I want to create something and give it shape. Therefore, I see programming as just a means to an end.

The first technology I learned

My first programming language was Python, and I've been doing contests and internships in Python ever since. And I often use Django when writing server-side code. This is because Django was the first framework I ever touched, and I used Django+DRF in my internship.

Disadvantages of Django

However, Django has some disadvantages.

Looking for a new framework

So I decided to touch a new framework.
As mentioned above, I did not want to learn a new language because what I want to do is create things and programming is just a means to an end. (I was trying to get introduced to the Go language at one point, but gave it up due to lack of time).
After much research, I learned that a framework called FastAPI was coming along quite well.

Advantages of FastAPI

When I touched FastAPI, it was a revolution.

  • I can include typedefs in Python.
    • I was very happy to see this feature, as I had a hard time with DX dropping due to the lack of types in Django development at my internship.
  • Ability to output API documentation by default.
    • Genius.
    • It's very effective in the division of labor with the frontend.
  • fast.

Disadvantages of FastAPI

There are a lot of advantages as mentioned above, but I still miss Django.

  • Admin Page.
    • This is still where the beauty of Django lies!
    • I really want it.
  • ORM
    • My hands have grown comfortable with Django's ORM.

Then...

Let's merge FastAPI and Django!!!

Code 👨🏻‍💻

https://github.com/kathmandu777/fastapi-django-template

Directory structure

├── README.md
├── docker-compose.yml
├── fastapi
│   ├── Dockerfile
│   ├── app
│   │   ├── __init__.py
│   │   ├── admin
│   │   │   ├── __init__.py
│   │   │   └── user.py
│   │   ├── api
│   │   │   ├── __init__.py
│   │   │   ├── auth.py
│   │   │   └── user.py
│   │   ├── apps.py
│   │   ├── dependencies
│   │   │   ├── __init__.py
│   │   │   └── auth.py
│   │   ├── migrations
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models
│   │   │   ├── __init__.py
│   │   │   ├── base.py
│   │   │   └── user.py
│   │   ├── routers
│   │   │   ├── __init__.py
│   │   │   ├── auth.py
│   │   │   ├── health.py
│   │   │   └── user.py
│   │   └── schemas
│   │       ├── __init__.py
│   │       ├── auth.py
│   │       └── user.py
│   ├── config
│   │   ├── __init__.py
│   │   ├── asgi.py
│   │   ├── exceptions.py
│   │   ├── jwt.py
│   │   ├── log.py
│   │   ├── password.py
│   │   ├── settings
│   │   │   ├── base.py
│   │   │   ├── local.py
│   │   │   └── production.py
│   │   └── urls.py
│   ├── fastapi.env
│   ├── manage.py
│   ├── media
│   ├── poetry.lock
│   ├── pyproject.toml
│   ├── scripts
│   │   ├── runlocalserver.sh
│   │   └── startserver.sh
│   └── static
│       └── admin
├── poetry.lock
└── pyproject.toml
Enter fullscreen mode Exit fullscreen mode

I use Django as a reference, and use a separate style for each application.

  • models: Django ORM
  • routers: FastAPI routers
  • schemas: FastAPI Pydantic models
  • api: FastAPI view

mounts

"""
Django settings
"""
django_app = get_asgi_application()


"""
FastAPI settings
"""
from app.routers import auth_router, health_router, user_router

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

fastapi_app = FastAPI()

# routers
fastapi_app.include_router(health_router, tags=["health"], prefix="/health")

# to mount Django
fastapi_app.mount("/django", django_app)
fastapi_app.mount("/static", StaticFiles(directory="static"), name="static")
fastapi_app.mount("/media", StaticFiles(directory="media"), name="media")
Enter fullscreen mode Exit fullscreen mode

Create an asgi application for Django and FastAPI, and mount the Django application from the FastAPI application.

Models

Same as when creating in Django.

class User(AbstractBaseUser, PermissionsMixin, BaseModelMixin):
    objects = UserManager()

    email = models.EmailField(_("email address"), unique=True)

    MIN_LENGTH_USERNAME = 1
    MAX_LENGTH_USERNAME = 20
    username = models.CharField(
        _("username"),
        max_length=MAX_LENGTH_USERNAME,
    )

    # permissions
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    ...
Enter fullscreen mode Exit fullscreen mode

Schemas

from uuid import UUID

from pydantic import BaseModel


class ReadUserSchema(BaseModel):
    uuid: UUID
    username: str
    email: str

    class Config:
        orm_mode = True


class CreateUserSchema(BaseModel):
    username: str
    email: str
    password: str
Enter fullscreen mode Exit fullscreen mode

orm_mode = True is important.

Routers

Same as when creating in FastAPI.

user_router = APIRouter()


@user_router.get("/", response_model=ReadUserSchema)
async def get(request: Request, current_user: User = Depends(get_current_user)) -> User:
    return UserAPI.get(request, current_user)


@user_router.post(
    "/",
    response_model=ReadUserSchema,
)
async def create(request: Request, schema: CreateUserSchema) -> User:
    return await UserAPI.create(request, schema)
Enter fullscreen mode Exit fullscreen mode

API

class UserAPI:
    @classmethod
    def get(cls, request: Request, current_user: User) -> User:
        return current_user

    @classmethod
    async def create(cls, request: Request, schema: CreateUserSchema) -> User:
        user = await User.objects.filter(email=schema.email).afirst()
        if user:
            raise HTTPException(status_code=400, detail="Email already registered")
        schema.password = hash_password(schema.password)
        return await sync_to_async(User.objects.create)(**schema.dict())
Enter fullscreen mode Exit fullscreen mode

It is important to apply asynchronous processing to the DB using sync_to_async, afirst, etc. Synchronous processing will be very slow.

Asynchronous Processing in Django 4.1 🐍

Django 4.1 has been updated to allow asynchronous processing in the ORM, so you can now develop with a combination of FastAPI and Django like this. All of the reference articles below do not do asynchronous processing, so I expect them to be quite slow.

Finally 🏁

I am happy to build my ideal style. Since it is in the template repository, I hope you will use it.
As I am new to FastAPI, I am sure there are some weird things I am doing. Please send me Issue, PR.

References 📚

Using FastAPI with Django - Stavros' Stuff

https://www.stavros.io/posts/fastapi-with-django
https://qiita.com/Ningensei848/items/ac72ff6edf4d887cdcc1

My encounter with this article was the beginning of this project.

The integration of FastAPI and Django ORM

https://kigawas.me/posts/integrate-fastapi-and-django-orm/
https://github.com/kigawas/fastapi-django

Most helpful.

Learn to Use Django with FastAPI Frameworks

https://nsikakimoh.com/learn/django-and-fastapi-combo-tutorials

I found it while writing this article.
I think it differs from this project in that it uses wsgi to run Django and does not support async for the Django ORM.

Top comments (1)

Collapse
 
ivansparq profile image
Ivan N

Thank you, it was usper useful.
I really like your repo and stole some of your precommit config :D