Skip to content
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

Filters in automatic crud router #41

Open
mick-net opened this issue Apr 3, 2024 · 1 comment
Open

Filters in automatic crud router #41

mick-net opened this issue Apr 3, 2024 · 1 comment
Labels
Automatic Endpoint Related to automatic endpoint creation functionality enhancement New feature or request

Comments

@mick-net
Copy link

mick-net commented Apr 3, 2024

First of all: I love FastCrud

Is your feature request related to a problem? Please describe.
Using the automatic crud_router functionality I'm missing the option to filter (all) routes on a specific column.
For example the automatic crud router works great when using a Todo model with

id: int
name: str
description: str
completed: bool

However, when I add a user_id to the todo model I cannot find any (advanced) option to filter the automatically generated routes on a value (like the user_id of the currently logged-in user). Currently, I'm manually creating e.g. a fastcrud.get route that manually adds user_id kwarg.

Describe the solution you'd like
I'm not sure what the best option would be but maybe the following crud_router arguments: read_filter, create_filter, update_filter, delete_filter can be added.

Example:

todo_router = crud_router(
       ...
       read_filter={ 'user_id': lambda: request.state.user_id }
       ...
)

Would do something like this under the hood:

todo_crud.get(db, schema_to_select=TodoRead, return_as_model=False, id=id, **read_filter)

Describe alternatives you've considered
Currently manually creating each router with a filter variables. So while this is still very doable it still feels like a lot of repetitive code of you have to add this filter for all CRUD routes. For example:

@todos_fastcrud.get("/{id}", response_model=TodoRead)
async def get_todo_fastcrud(id: int, request: Request, db: AsyncSession = Depends(get_session)):
    user_id = request.state.user_id
    return await todo_crud.get(db, schema_to_select=TodoRead, return_as_model=False, id=id, user_id=user_id)

Additional context
Maybe I'm missing something and this is already implemented?
I see a request for filters in the multi get, but this request is still different I think:
#15

@mick-net mick-net added the enhancement New feature or request label Apr 3, 2024
@igorbenav
Copy link
Owner

igorbenav commented Apr 5, 2024

Hey, @mick-net, I'm really glad you like it!

It's currently possible to do something like this, but not simple. You could just write a subclass of endpointcreator that accepts a filter (like the id), and passes it to all the endpoint methods.

You would have to explicitly pass it to the endpoint methods in this subclass, but once it's ready you can just pass this custom EndpointCreator to crud_router.

You could do something like:
creating a dependency to get the user

# creating a dependency to get the user

from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

async def get_user_id(token: str = Depends(oauth2_scheme)):
    # Here, you would decode the token, verify it, and extract the user_id
    user_id = decode_token_to_user_id(token)  # Implement this function based on your auth system
    if not user_id:
        raise HTTPException(status_code=401, detail="Invalid token or expired token.")
    return user_id

Overwrite all methods in EndpointCreator with the custom logic

# overwrite all methods in EndpointCreator
# the only actual change is the new dependency in the endpoint function plus the filter in the crud method

class UserFilteredEndpointCreator(EndpointCreator):
    def _read_item(self):
        """Override to apply user_id filter for single item read."""
        @apply_model_pk(**self._primary_keys_types)
        async def endpoint(
            db: AsyncSession = Depends(self.session), 
            user_id: int = Depends(get_user_id), **pkeys
        ):
            item = await self.crud.get(db, **pkeys, user_id=user_id)
            if not item:
                raise NotFoundException(detail="Item not found")
            return item

        return endpoint

    def _read_items(self):
        """Override to apply user_id filter for reading multiple items."""
        async def endpoint(
            db: AsyncSession = Depends(self.session),
            user_id: int = Depends(get_user_id),
            offset: int = Query(0),
            limit: int = Query(100),
        ):
            return await self.crud.get_multi(db, offset=offset, limit=limit, user_id=user_id)

        return endpoint

    # do it for all relevant methods
    ...

Pass this custom EndpointCreator to crud_router:

app.include_router(
    crud_router(
        session=async_session,
        model=YourModel,
        crud=FastCRUD(YourModel),
        create_schema=CreateYourModelSchema,
        update_schema=UpdateYourModelSchema,
        delete_schema=DeleteYourModelSchema,
        endpoint_creator=UserFilteredEndpointCreator,  # Use your subclass here
        path="/yourmodel",
        tags=["YourModel"],
    )
)

And it would work properly. I agree that this is not the best way to do it, and I think it would be a nice feature, but I'm first making sure FastCRUD (the method) is rock solid before moving on to new features in EndpointCreator, since the latter depends on the former.

So, unless someone works on it, I'll do it not too much in the future!

@igorbenav igorbenav added the Automatic Endpoint Related to automatic endpoint creation functionality label Apr 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Automatic Endpoint Related to automatic endpoint creation functionality enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants