Class-based operations
Warning
This is only a proposal and is not in the library code, but eventually it may become part of Django Ninja.
Please consider adding a like/dislike or comment in the github issue to express your feelings about this proposal
The problem
An API operation is a callable that accepts a request and parameters and returns a response, but in the real world, it often happens that the same piece of code needs to be reused across multiple operations. Let's look at the following example:
- We have a todo application with projects and tasks
- Each project has multiple tasks
- Each project may also have an owner (a user)
- Users should not be able to access projects they do not own
The model structure is roughly this:
class Project(models.Model):
title = models.CharField(max_length=100)
owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)
class Task(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
completed = models.BooleanField()
Now, let's create some API operations for this: - list of tasks of a project - some task details - a "complete task" operation
The code should validate that a user can only access tasks from their own projects (otherwise, return 404)
It can look like this:
router = Router()
@router.get('/project/{project_id}/tasks/', response=List[TaskOut])
def task_list(request):
user_projects = request.user.project_set
project = get_object_or_404(user_projects, id=project_id))
return project.task_set.all()
@router.get('/project/{project_id}/tasks/{task_id}/', response=TaskOut)
def details(request, task_id: int):
user_projects = request.user.project_set
project = get_object_or_404(user_projects, id=project_id))
user_tasks = project.task_set.all()
return get_object_or_404(user_tasks, id=task_id)
@router.post('/project/{project_id}/tasks/{task_id}/complete', response=TaskOut)
def complete(request, task_id: int):
user_projects = request.user.project_set
project = get_object_or_404(user_projects, id=project_id))
user_tasks = project.task_set.all()
task = get_object_or_404(user_tasks, id=task_id)
task.completed = True
task.save()
return task
As you can see, these lines are repeated to check permissions:
user_projects = request.user.project_set
project = get_object_or_404(user_projects, id=project_id))
You can extract this into a function, but that would only make it 3 lines shorter and it would still be quite messy...
The solution
The proposal is to have an alternative called "class-based operations", where you can decorate a whole class with a path decorator:
from ninja import Router
router = Router()
@router.path('/project/{project_id}/tasks')
class Tasks:
def __init__(self, request, project_id=int):
user_projects = request.user.project_set
self.project = get_object_or_404(user_projects, id=project_id))
self.tasks = self.project.task_set.all()
@router.get('/', response=List[TaskOut])
def task_list(self, request):
return self.tasks
@router.get('/{task_id}/', response=TaskOut)
def details(self, request, task_id: int):
return get_object_or_404(self.tasks, id=task_id)
@router.post('/{task_id}/complete', response=TaskOut)
def complete(self, request, task_id: int):
task = get_object_or_404(self.tasks, id=task_id)
task.completed = True
task.save()
return task
All the common initialization and permission checks go into the constructor:
@router.path('/project/{project_id}/tasks')
class Tasks:
def __init__(self, request, project_id=int):
user_projects = request.user.project_set
self.project = get_object_or_404(user_projects, id=project_id))
self.tasks = self.project.task_set.all()
This makes the main business operations focus only on the tasks (exposed as the self.tasks attribute)
You can use the api and router instances to support class paths.
The problem
The __init__ method:
def __init__(self, request, project_id=int):
Python does not support the async keyword for __init__, so to support async operations we need another initialization method, but __init__ sounds the most logical.
Your thoughts/proposals
Please give your thoughts/likes/dislikes about this proposal in the github issue.
Comments
本文总阅读量次
