Add a new feature - Backend

In this tutorial, we walk through adding a new feature to the application. The focus is on the backend.


You have launched your app to production in 5mins. Congrats. You are collecting emails for the concept, but now you have to do the dirty work to build a feature. You are in luck. It is very easy.

The tutorial will cover the entire workflow, from understanding feature requests to creating a pull request.

Understanding Feature Requests

The first step in adding a new feature is understanding the feature request.

User stories are a good way to think about what you want to build. It puts you in the perspective of the user. For example

As a user i want to create a ‘store’ app where I can see all of my employees, their departments and update their details

Based on this we can come up with a list of features to build.

  • Model - Store employees and departments

  • Views - Create API, InertiaJS views

  • Schemas - (or forms if you prefere) - Define my data input and output structures or Da

Creating a Branch

Next, you need to create a new branch for the feature. Open your terminal, navigate to your project directory, and run the following command to create and switch to a new branch:

text
$ git checkout -b feature-branch-name

Planning

Break down the feature request into smaller tasks. Write down these tasks and prioritize them. For example, if you’re adding a new webhooks feature, your tasks might include:

  • Adding a model with fields for name, URL, and date sent

  • Creating a new webhooks endpoint

  • Adding API views and wiring up the URLs

  • Displaying a list of all the webhooks

Backend Model Work

Identify if the feature requires changes to the data model. If changes are required, update the models in your Django application.

python
from apps.tenants.mixins import TenantModelManager
from apps.tenants.models import TenantModelMixin

class Department(models.Model):
    title = models.CharField(max_length=100)

class EmployeeManager(TenantModelManager)
    def change_employee_department(self, data) -> Employee | None:
        employee = self.filter(id=data.id).first()
        if employee is None:
            return None
        employee.department = data.department
        employee.save()
        return employee
      

class Employee(TenantModelMixin):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    department = models.ForeignKey(Department, on_delete=models.CASCADE)
    birthdate = models.DateField(null=True, blank=True)
    cv = models.FileField(null=True, blank=True)
  • Keep business logic in the manage or create a new services file. Do not add this business logic to the view. It will make your app 10x times simpler to test.

  • Use the TenantModelMixin and TenantModelManager for both

API Addition with Django Ninja

Create a new API endpoint in Django using Django Ninja. Define the necessary HTTP methods (GET, POST, etc.) for the endpoints and schemas.

Create the schemas

python
class EmployeeInSchema(Schema):
    first_name: str
    last_name: str
    department_id: int = None
    birthdate: date = None

    class Config:
        alias_generator = humps.camelize
        allow_population_by_field_name = True



class EmployeeOutSchema(Schema):
    id: int
    first_name: str
    last_name: str
    department_id: int = None
    birthdate: date = None

    class Config:
        alias_generator = humps.camelize
        allow_population_by_field_name = True

Create the API endpoints

This is straight from the django ninja docs

text

@api.post(
    "/employees/", 
    response="EmployeeOutSchema", 
    operationId="createEmployee",
    by_alias=True
)
def create_employee(request, payload: EmployeeIn):
    employee = Employee.objects.create(**payload.dict())
    return {"id": employee.id}


@api.get(
    "/employees/{employee_id}/", 
     response=EmployeeOutSchema,
    operationId="getEmployee",
    by_alias=True
)
def get_employee(request, employee_id: int):
    employee = get_object_or_404(Employee, id=employee_id)
    return employee


@api.get(
    "/employees/", 
    response=List[EmployeeOutSchema],
    operationId="listEmployee",
    by_alias=True
)
def list_employees(request):
    qs = Employee.objects.all()
    return qs


@api.put(
    "/employees/{employee_id}/",
    response=EmployeeSchema,
    operationId="updateEmployee",
    by_alias=True
)
def update_employee(request, employee_id: int, payload: EmployeeIn):
    employee = get_object_or_404(Employee, id=employee_id)
    for attr, value in payload.dict().items():
        setattr(employee, attr, value)
    employee.save()
    employee.refresh_from_db()
    return employee


@api.delete(
    "/employees/{employee_id}",
    response=EmployeeOutSchema,
    operationId="deleteEmployee",
    by_alias=True
)
def delete_employee(request, employee_id: int):
    employee = get_object_or_404(Employee, id=employee_id)
    employee.delete()
    return {"success": True}
  • We added response to the api model. This will expose the correct schemas on the frontend.

  • Also include tags for EVERY endpoint. We can that access the data via the tags interface in SwaggerJS

InertiaJS Views

Next step is to create the views. You probably at need at least one view to show the list. The views for the detail and edit page are optional.

text
from django.shortcuts import get_object_or_404
from apps.common.inertia import inertia

@inertia("app/store/employees")
@login_required
def employees(request):
    return {} # <- you can return a list of the apis here if you prefer

@inertia("app/store/employee-detail")
@login_required
def employee_detail(request, id):
    get_object_or_404(Employee, id=id)
    return {} # <- you can return the object here if you want
  • import the inertia render decorator from the common module.

URLs Wiring

Add a new URL pattern in Django’s urls.py for your InertiaJS view. Test the URL to ensure it’s correctly wired to the view.

Frontend Components

Identify the frontend components needed for the feature (e.g., a datatable). Create these components using InertiaJS and ensure they interact correctly with your API.

Checkout the frontend section for how to create queries and mutations for the form, add a list view

Testing

Write tests for your models, APIs, and views. Run the tests to ensure everything is working as expected.

Committing Changes

Stage your changes with git add . , then commit your changes with git commit -m "commit message" .

Creating a Pull Request

Push your branch to GitHub with git push origin feature-branch-name . Then, go to your repository on GitHub and create a new pull request for your branch.

This tutorial provides a step-by-step guide to working with Django, Django Ninja, InertiaJS, and Git. It assumes that you have a basic understanding of these tools.