Django Ninja CRUD is a powerful, declarative, and yet opinionated framework that simplifies the development of CRUD (Create, Read, Update, Delete) endpoints and tests with Django Ninja. It promotes best practices for efficient, robust endpoint creation, allowing you to focus on what matters most: solving real problems. Initially inspired by DRF's ModelViewSet, Django Ninja CRUD evolved to address its limitations, adopting a composition-over-inheritance approach to achieve true modularity – a foundational step towards a broader declarative interface for endpoint creation.
I'm excited to share my recent article: "Introducing Django Ninja CRUD" on Medium. This piece dives into the journey of creating Django Ninja CRUD, detailing its features, benefits, and the paradigm shift it brings to Django development.
- Purely Declarative: Embrace an approach where defining views and tests is a matter of declaring what you want, not how to achieve it.
- Unmatched Modularity: Tailor your viewsets with the desired CRUD views and customize each view's behavior with ease. Extend the flexibility by creating your own subclasses of the provided views and tests.
- Powerful Testing Framework: Leverage a matrix-based testing framework for defining diverse test scenarios declaratively.
- Focus on What Matters: Spend more time solving real-world problems and less on CRUD boilerplate.
Its blend of declarative syntax, modularity, and powerful testing capabilities sets a new standard for developers seeking efficiency and precision.
Django Ninja CRUD is not just a tool; it's a paradigm shift in Django web application development and testing.
pip install django-ninja-crud
For more information, see the installation guide.
Let's imagine you're building a system for a university and you have a model called Department
. Each department in your university has a unique title.
# examples/models.py
from django.db import models
class Department(models.Model):
title = models.CharField(max_length=255, unique=True)
To interact with this data, we need a way to convert it between Python objects and a format that's easy to read and write (like JSON). In Django Ninja, we do this with Schema
:
# examples/schemas.py
from ninja import Schema
class DepartmentIn(Schema):
title: str
class DepartmentOut(Schema):
id: int
title: str
The DepartmentIn
schema defines what data we need when creating or updating a department. The DepartmentOut
schema defines what data we'll provide when retrieving a department.
Now, here comes the power of Django Ninja CRUD. With it, you can set up the CRUD operations for the Department
model with just a few lines of code:
# examples/views/department_views.py
from django.http import HttpRequest
from ninja import Router
from ninja_crud import views, viewsets
from examples.models import Department
from examples.schemas import DepartmentIn, DepartmentOut
router = Router()
class DepartmentViewSet(viewsets.ModelViewSet):
model = Department
default_input_schema = DepartmentIn
default_output_schema = DepartmentOut
list_departments = views.ListModelView()
create_department = views.CreateModelView()
retrieve_department = views.RetrieveModelView()
update_department = views.UpdateModelView()
delete_department = views.DeleteModelView()
# The register_routes method must be called to register the routes
DepartmentViewSet.register_routes(router)
# Beyond the CRUD operations managed by the viewset,
# the router can be used in the standard Django Ninja way
@router.get("/statistics/", response=dict)
def get_department_statistics(request: HttpRequest):
return {"total": Department.objects.count()}
A key advantage of this package is that it makes your views easy to test. Once you've set up your CRUD operations, you can write tests to ensure they're working as expected. Here's an example of how you might test the Department
operations:
# examples/tests/test_department_views.py
from ninja_crud import testing
from examples.models import Department
from examples.views.department_views import DepartmentViewSet
class TestDepartmentViewSet(testing.viewsets.ModelViewSetTestCase):
model_viewset_class = DepartmentViewSet
base_path = "api/departments"
@classmethod
def setUpTestData(cls):
cls.department_1 = Department.objects.create(title="department-1")
cls.department_2 = Department.objects.create(title="department-2")
@property
def path_parameters(self):
return testing.components.PathParameters(
ok={"id": self.department_1.id},
not_found={"id": 9999}
)
@property
def payloads(self):
return testing.components.Payloads(
ok={"title": "department-3"},
bad_request={},
conflict={"title": self.department_2.title},
)
test_list_departments = testing.views.ListModelViewTest()
test_create_department = testing.views.CreateModelViewTest(payloads)
test_retrieve_department = testing.views.RetrieveModelViewTest(path_parameters)
test_update_department = testing.views.UpdateModelViewTest(path_parameters, payloads)
test_delete_department = testing.views.DeleteModelViewTest(path_parameters)
# You can then add additional tests as needed
def test_get_department_statistics(self):
response = self.client.get(f"{self.base_path}/statistics/")
self.assertEqual(response.status_code, 200)
... # Additional assertions
For more information, see the documentation.
First and foremost, a heartfelt thank you for taking an interest in this project. If it has been helpful to you or you believe in its potential, kindly consider giving it a star on GitHub. Such recognition not only fuels my drive to maintain and improve this work but also makes it more visible to new potential users and contributors.
If you've benefited from this project or appreciate the dedication behind it, consider showing further support. Whether it's the price of a coffee, a word of encouragement, or a sponsorship, every gesture adds fuel to the open-source fire, making it shine even brighter. ✨
Your kindness and support make a world of difference. Thank you! 🙏