DEV Community

Cover image for Full Stack Todo WebApp With React and Python-Django.
Kwaku Duah
Kwaku Duah

Posted on

Full Stack Todo WebApp With React and Python-Django.

This is a detailed tutorial on how to build a full stack To-Do application using React Javascript Framework and Django Web Framework.

React is a framework that is used for building Single Page Applications(SPA's).React was introduced by Facebook now Meta and has a wonderful community of developers and a slick documentation.

Django on the other hand is a simple web framework that aims to simplify web development.Django framework have a lot of ready made libraries and a vibrant documentation that supports developer needs.

In this application, React Framework serves as the client-side that handles the UI(User Interface). It also gets and sends data via requests to the Django Framework backend with an API build atop the Django REST Framework(DRF).

This is a demo of the application:
This web application allows users to create tasks, mark the tasks as complete or incomplete.

  • PREREQUISTES
  • BACKEND
  • API
  • SERIALIZERS
  • FRONTEND

PREREQUISITES
In this tutorial, you must have:

  • Set up a local programming environment for python3

  • Set up Node.js on your Local Development Environment.

SETTING UP DJANGO BACKEND
This project was built entirely on a Linux machine, however, with slight modifications,the commands are just about the same on other Operating Systems(0S).

Under this section, a new project directory will be created where Django will be installed.
Open a new terminal and create a project directory:

mkdir react-django-todo

Navigate into the directory by issuing the {cd} (change directory) command on the terminal.

cd react-django-todo

Install Pipenv using pip in order to create a virtual environment.
Depending on your OS, you may need to use pip3 instead of pip

pip install pipenv

Now activate the virtual environment by running the command:
pipenv shell

Install Django in the virtual environment by running the command:

pipenv install django

This allows us to use any version of Django, Python Programming Language and all other dependencies in a closed environment/sandboxed without breaking anything on our system.

Next, we create a new project directory backend with either of the commands:

django-admin startproject backend or
python3 manage.py startproject backend

Note: Depending on your installation or OS, you may need to use python3 or python.

Navigate to the newly created directory:

cd backend

Create and start a new application in the same directory:

python3 manage.py startapp todo

In the next steps, perform migrations:

python3 manage.py migrate

Start up the server with the command:

python3 manage.py runserver

Press and hold the Ctrl Key and click on the link http://localhost:8000 to open it in your browser.

Django Installation

REGISTERING THE todo APPLICATION IN DJANGO
Django applications are modular as pieces of functionality are built into them. The setup for the backend is now complete and all applications in Django are registered as installed apps so Django can recognize them.
INSTALLED_APPS can be found in the directory 'backend/setting.py'.

todo app

Django comes with SQLite database which is minimal and great for side projects.

CREATING A todo MODEL
In this section, we will create a model that describes how todo items should be saved in the database.

Navigate to the directory 'todo/models.py' and add the lines of code:

from django.db import models

# Create your models here.

class Todo(models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField()
    completed = models.BooleanField(default=False)

    def _str_(self):
        return self.title
Enter fullscreen mode Exit fullscreen mode

This code describes three properties of the todo model.

  1. title of the task

  2. description of the task

3.status of the task, completed or not completed

Once a model has been created, we need to migrate the model into the database:

python3 manage.py makemigrations todo

In the next step, the changes will be applied to the database:
python3 manage.py migrate

You may now test the Create, Read, Update and Delete (CRUD) operations on the Todo application by using the admin interface provided by Django by default.

Open todo/admin.py and the following code:

from django.contrib import admin
from .models import Todo

class TodoAdmin(admin.ModelAdmin):
    list_display = ('title', 'description', 'completed')

(Register your models here).

admin.site.register(Todo, TodoAdmin)

Enter fullscreen mode Exit fullscreen mode

The next step is to create a "superuser" account to access the admin interface. Run the command in the terminal:

python3 manage.py createsuperuser

This command sends a prompt that requests for 'username', 'email address', and 'password'.Enter a username and password you will remember because you will use it to log into the admin dashboard interface.

Start the server again with the command:

python3 manage.py runserver

Navigate to the link http://localhost:8080/admin in your browser to access the admin interface and log in with the username and password you created earlier.

Admin Django Dashboard

It is now possible to CRUD the Todo items from this dashboard.

You can now add items to the admin dashboard.

ToDo

SETTING UP APIs

In this part, Django REST framework will be used to create the API(Application Programming Interface).
While still in the virtual environment, install the djangorestframework and django cross origin resource sharing headers with the commands:


pipenv install djangorestframework django-cors-headers

Next we need to add the djangorestframework and django-cors-headers to the list of installed apps in the todo/settings.py file.

django cors

Still in the todo/settings.py file, add the following lines at the bottom of the file:

CORS_ORIGIN_WHITELIST = [
     'http://localhost:3000'
]
Enter fullscreen mode Exit fullscreen mode

Django CORS is mechanism that allows clients to consume APIs that are hosted on a different domain. In other words, it is a library in Python that ensures that specific sets of headers that are required by servers are served by browsers.
The CORS_ORIGIN_WHITELIST code means that the domain 'localhost:3000' has been whitelisted and the browser will allow Cross Origin Resources to be shared on that domain.
The frontend of the application will be served on that port hence whitelisting it so the frontend can communicate with the backend.

CREATING SERIALIZERS
Serializers in Python-Django convert model instances to JSON(Javascript Object Notation) enabling the frontend to work with the received data from the backend.

Create a todo/serializers.py file and update it with the following lines:

from rest_framework import serializers
from .models import Todo

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ('id', 'title', 'description', 'completed')
Enter fullscreen mode Exit fullscreen mode

The model data will be converted to JSON format.

CREATING View
In this section, we wil create a TodoView class in the todo/views.py file. Add the following lines to the todo/views.py:

from django.shortcuts import render
from rest_framework import viewsets
from .serializers import TodoSerializer
from .models import Todo

class TodoView(viewsets.ModelViewSet):
    serializer_class = TodoSerializer
    queryset = Todo.objects.all()
Enter fullscreen mode Exit fullscreen mode

Viewsets class in Django provides implementation for CRUD operations by default. In the above code, there is a serializer class and a queryset.

We need to update the backend/urls.py file with the view we just created. Open the backend/urls.py file and add the following lines:

from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from todo import views

router = routers.DefaultRouter()
router.register(r'todos', views.TodoView, 'todo')

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include(router.urls)),
]
Enter fullscreen mode Exit fullscreen mode

URL path for the API has been indicated in the above code. API has finally been created and this section has now been completed.

CRUD operations can now be performed on the Todo model.
The router class allows us to make the following queries:
/todos/ - returns a list of Todo items. CREATING of an item, an item can be performed here
/todos/id - returns a single item using the 'id' primary key where UPDATING and DELETION are allowed.

Restart the server with the command;
python3 manage.py runserver

todo app

SETTING UP THE FRONTEND
In this section, we will complete the web application by writing the frontend.

Open another terminal and navigate to the directory 'react-django-todo'
In this tutorial, I will use npx to Create-React-App:

npx create-react-app frontend

cd frontend

Start the application by running the command:

npm start

The browser will open the http://localhost:3000.
In the next step, we will install bootstrap and reactstrap

npm install boostrap reactstrap --legacy-peer-deps

In a scenario where your system encounters a dependency issue based either a incompatible ReactJS, Bootstrap or Reactstrap package, visit this link for further guidelines.

Open frontend/index.js and paste these lines:

import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Enter fullscreen mode Exit fullscreen mode

Open frontend/App.js and paste these lines:

import React, { Component } from "react";

const todoItems = [
  {
    id: 1,
    title: "Go to Market",
    description: "Buy ingredients to prepare dinner",
    completed: true,
  },
  {
    id: 2,
    title: "Study",
    description: "Read Algebra and History textbook for the upcoming test",
    completed: false,
  },
  {
    id: 3,
    title: "Sammy's books",
    description: "Go to library to return Sammy's books",
    completed: true,
  },
  {
    id: 4,
    title: "Article",
    description: "Write article on how to use Django with React",
    completed: false,
  },
];

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      viewCompleted: false,
      todoList: todoItems,
    };
  }

  displayCompleted = (status) => {
    if (status) {
      return this.setState({ viewCompleted: true });
    }

    return this.setState({ viewCompleted: false });
  };

  renderTabList = () => {
    return (
      <div className="nav nav-tabs">
        <span
          className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
          onClick={() => this.displayCompleted(true)}
        >
          Complete
        </span>
        <span
          className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
          onClick={() => this.displayCompleted(false)}
        >
          Incomplete
        </span>
      </div>
    );
  };

  renderItems = () => {
    const { viewCompleted } = this.state;
    const newItems = this.state.todoList.filter(
      (item) => item.completed == viewCompleted
    );

    return newItems.map((item) => (
      <li
        key={item.id}
        className="list-group-item d-flex justify-content-between align-items-center"
      >
        <span
          className={`todo-title mr-2 ${
            this.state.viewCompleted ? "completed-todo" : ""
          }`}
          title={item.description}
        >
          {item.title}
        </span>
        <span>
          <button
            className="btn btn-secondary mr-2"
          >
            Edit
          </button>
          <button
            className="btn btn-danger"
          >
            Delete
          </button>
        </span>
      </li>
    ));
  };

  render() {
    return (
      <main className="container">
        <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
        <div className="row">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <div className="mb-4">
                <button
                  className="btn btn-primary"
                >
                  Add task
                </button>
              </div>
              {this.renderTabList()}
              <ul className="list-group list-group-flush border-top-0">
                {this.renderItems()}
              </ul>
            </div>
          </div>
        </div>
      </main>
    );
  }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This code is although not dynamic as some values have been hardcoded, but then they are only temporary as once the data is fetched from the backend they are replaced.

The renderTablist() function helps span what is displayed. Clicking the Completed Tab will display completed tasks and clicking the Incomplete Tab will display the yet to be completed tasks.

Save changes and from the two terminals, serve the frontend and backend at the same time.

Full App

Source code is available for free here github

Top comments (0)