Taula de continguts

Integració de Django amb Reactjs

De manera simplificada, Reactjs és una biblioteca Javascript per desenvolupar aplicacions de pàgina única (SPA), amb una documentació molt detallada i ben estructurada. Per a aquest exemple, on gestionarem clients i els seus comptes associats, React servirà com a frontend, gestionant la interfície d'usuari (UI) a través de la petició al framework de Django i amb l'ajuda de Django REST Framework.

Referències:

, , , , , , , , ,

Creació del projecte

Els passos a seguir per crear el projecte a Django són els següents (si tens dubtes o vols repassar el procés, et recomano que revisis el tutorial):

1. Crea el projecte «project» a l'entorn virtual. Recorda instal·lar Django dins l'entorn virtual activat

2. Crea l'aplicació «customers».

3. Configura el projecte (urls, localització) i verifica que s'accedeix correctament des del navegador.

4. Crea el model de dades (dins del fitxer models.py de l'aplicació customers). En aquest cas, treballarem amb les dues classes següents:

Proposta d'implementació Customer i Account
models.py
class Project(models.Model):
    class Customer(models.Model):
        customer_id = models.IntegerField(default=0)
        customer_name = models.CharField(max_length=200)
        def __str__(self):
            return self.customer_name
 
    class Account(models.Model):
        account_id = models.IntegerField(default=0)
        customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
        description = models.CharField(max_length=200)
        limit = models.IntegerField(default=0)
 
        def __str__(self):
            return self.description
 

Aquest model no contempla que les claus primàries siguin autonumèriques ni la cardinalitat de la interrelació (1 client pot tenir N comptes, però 1 compte només pot estar associat a 1 client). Com ho faries? Recorda que estem treballant a SQlite. Quins passos caldria fer per treballar contra una base de dades MySQL?

5. Registra el model de dades (a admin.py), afegeix l'aplicació (a settings.py) i migra el model per poder realitzar les operacions CRUD.

6. Crea el superadmin, comprova que pots accedir correctament al site d'administració i executa instruccions CRUD per validar el model. Hauries de veure una pantalla similar a aquesta:

Integració de Reactjs (Creació de l'API)

Important! Verifica que tens instal·lat node.js a la teva màquina i, en cas contrari, instal·la la darrera versió.

1. Instal·la node.js al teu entorn virtual:

(env) $ apt install nodejs


2. Instal·la Django REST Framework

(env) $ pip install djangorestframework


3. Instal·la Django-cors-headers

(env) $ pip install django-cors-headers


4. Obre el fitxer setting.py i afegeix les dues dependències que s'acaben d'instal·lar a INSTALL_APPS com es mostra a continuació:

settings.py
INSTALLED_APPS = [
    'customers.apps.CustomersConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'corsheaders',
    'rest_framework',
]

També cal que el port localhost 3000 estigui en la llista blanca al fitxer settings.py. Si no ho fem, hi haurà un bloc entre el localhost:8000 i el localhost:3000. Afegeix el codi següent per a aconseguir el mateix:

settings.py
# White listing the localhost:3000 port
# for React
CORS_ORIGIN_WHITELIST = (
    'http://localhost:3000',
)

5. A la secció MIDDLEWARE cal afegir la configuració de les capçaleres dels cors, tal com es mostra a continuació:

settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'corsheaders.middleware.CorsMiddleware'
]

És important que vagis revisant el terminal, per veure que en cada modificació del fitxer settings.py no dóna error l'aplicació.

6. Ara hem de crear els Serializers per al model de dades de Customers. Els Serializers són responsables de convertir instàncies de model a JSON. Això ajudarà el frontend a treballar amb les dades rebudes fàcilment. El JSON és l'estàndard per a l'intercanvi de dades al web.

Crea un fitxer dins de la carpeta «customers» i anomena'l serializers.py. Dins de la carpeta afegeix el codi següent:

serializers.py
# import serializers from the REST framework
from rest_framework import serializers
 
# import the Customer data model
from .models import Customer
 
# create a serializer class
class CustomerSerializer(serializers.ModelSerializer):
 
    # create a meta class
    class Meta:
        model = Customer
        fields = ('customer_id', 'customer_name')

7. Ara és el moment de crear les vistes. Per tant, obre el fitxer views.py i afegeix el codi següent al fitxer:

views.py
# import view sets from the REST framework
from rest_framework import viewsets
 
# import the CustomerSerializer from the serializer file
from .serializers import CustomerSerializer
 
# import the Customer model from the models file
from .models import Customer
 
# create a class for the Customer model viewsets
class CustomerView(viewsets.ModelViewSet):
 
    # create a serializer class and
    # assign it to the CustomerSerializerclass
    serializer_class = CustomerSerializer
 
    # define a variable and populate it
    # with the Customer list objects
    queryset = Customer.objects.all()

8. Obriu el fitxer urls.py i afegeix el codi següent:

urls.py
from django.urls import include,path
from customers import views
 
# import routers from the REST framework
# it is necessary for routing
from rest_framework import routers
 
# create a router object
router = routers.DefaultRouter()
 
# register the router
router.register(r'customers',views.CustomerView, 'customers')
 
urlpatterns = [
    path("", views.index),
    path("customers/", include("customers.urls")),
    path("admin/", admin.site.urls),
    path('api/', include(router.urls))
]

Aquest és el pas final per a crear l'API REST. Ara podem realitzar totes les operacions CRUD. Els encaminadors ens permeten fer consultes. Per exemple, si anem a les «customers», això retornarà la llista de els clients.

Ara comprovem si estem avançant en la direcció correcta. Per tant, executeu el servidor i navegueu fins a l'URL següent:

localhost:8000/api

Si tot va bé, obtindrem el següent resultat:

Com podeu veure, la nostra API està en marxa. Ara, si provem d'accedir als «customers», podrem veure i interactuar amb les nostres tasques:

localhost:8000/api/customers

Fins i tot, podem canviar el format dels resultats (per exemple, retornar en formar JSON):

Integració de Reactjs (FrontEnd)

1. Crea una «boilerplate» de l'aplicació Js React:

npx create-react-app frontend

on npx significa Node Package Executable

Només funcionarà la sentència anterior amb versions de node.js superiors a la 14.0, així que si et dóna error, caldrà que l'actualitzis. Pots seguir aquest tutorial. Tingues paciència, perquè aquest pas trigarà força!

2. Instal·la reactstrap i bootstrap al projecte (per donar una mica d'estil al disseny de la interfície d'usuari):

npm install reactstrap bootstrap

3. Des de la terminal, accedeix al directori de frontend i executa la següent instrucció, per tal de verificar que tot funciona correctament:

npm start

Si és així, et sortirà una pantalla similar a aquesta (en l'adreça localhost:3000):

4. Obre el fitxer App.js a la carpeta del frontend. Elimina el codi existent i substitueix-lo pel codi següent:

App.js
import "./App.css";
 
function App() {
  return <div className="App"><h2>Gestió de clients</h2></div>;
}
 
export default App;

5. El pas anterior era un exemple per validar l'aplicació. Substitueix-lo, en el fitxer App.js, pel codi següent:

App.js
// import Component from the react module
import React, { Component } from "react";
import Modal from "./components/Modal";
import axios from 'axios'; 
 
class App extends Component {
  constructor(props) {
    super(props);
 
    this.state = {
 
      viewCompleted: false,
      activeItem: {
        title: "",
        description: "",
        completed: false
      },
 
      customersList: []
    };
  }
 
 
  componentDidMount() {
    this.refreshList();
  }
 
  refreshList = () => {
    axios   //Axios to send and receive HTTP requests
      .get("http://localhost:8000/api/customers/")
      .then(res => this.setState({ customersList: res.data }))
      .catch(err => console.log(err));
  };
 
  displayCompleted = status => {
    if (status) {
      return this.setState({ viewCompleted: true });
    }
    return this.setState({ viewCompleted: false });
  };
 
 
  renderTabList = () => {
    return (
      <div className="my-5 customer-list">
        <span
          onClick={() => this.displayCompleted(true)}
          className={this.state.viewCompleted ? "active" : ""}
        >
          completed
            </span>
        <span
          onClick={() => this.displayCompleted(false)}
          className={this.state.viewCompleted ? "" : "active"}
        >
          Incompleted
            </span>
      </div>
    );
  };
 
  renderItems = () => {
    const { viewCompleted } = this.state;
    const newItems = this.state.taskList.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={`customer-title mr-2 ${
            this.state.viewCompleted ? "completed-customer" : ""
          }`}
          title={item.description}
        >
          {item.title}
        </span>
        <span>
          <button
            onClick={() => this.editItem(item)}
            className="btn btn-secondary mr-2"
          >
            Edit
          </button>
          <button
            onClick={() => this.handleDelete(item)}
            className="btn btn-danger"
          >
            Delete
          </button>
        </span>
      </li>
    ));
  };
 
  toggle = () => {
    //add this after modal creation
    this.setState({ modal: !this.state.modal });
  };
 
  handleSubmit = (item) => {
    this.toggle();
     alert("save" + JSON.stringify(item));
    if (item.id) {
      // if old post to edit and submit
      axios
        .put(`http://localhost:8000/api/tasks/${item.id}/`, item)
        .then((res) => this.refreshList());
      return;
    }
 
    axios
      .post("http://localhost:8000/api/customers/", item)
      .then((res) => this.refreshList());
  };
 
  handleDelete = (item) => {
      alert("delete" + JSON.stringify(item));
    axios
      .delete(`http://localhost:8000/api/tasks/${item.id}/`)
      .then((res) => this.refreshList());
  };
 
  createItem = () => {
    const item = { title: "", description: "", completed: false };
    this.setState({ activeItem: item, modal: !this.state.modal });
  };
 
  editItem = (item) => {
    this.setState({ activeItem: item, modal: !this.state.modal });
  };
 
  render() {
    return (
      <main className="content">
        <h1 className="text-success text-uppercase text-center my-4">
          GFG Task Manager
        </h1>
        <div className="row ">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <div className="">
                <button onClick={this.createItem} className="btn btn-info">
                  Add task
                </button>
              </div>
              {this.renderTabList()}
              <ul className="list-group list-group-flush">
                {this.renderItems()}
              </ul>
            </div>
          </div>
        </div>
        {this.state.modal ? (
          <Modal
            activeItem={this.state.activeItem}
            toggle={this.toggle}
            onSave={this.handleSubmit}
          />
        ) : null}
      </main>
    );
  }
}
export default App;

6. Obre el fitxer Index.css, neteja el CSS que hi ha a dins i afegeix el següent codi al fitxer:

Index.css
.customer-title {
  cursor: pointer;
}
.completed-customer {
  text-decoration: line-through;
}
.customer-list > span {
  padding: 5px 8px;
  border: 1px solid rgb(7, 167, 68);
  border-radius: 10px;
  margin-right: 5px;
  cursor: pointer;
}
.customer-list > span.active {
  background-color: rgb(6, 139, 12);
  color: #fff;
}

7. Crea una carpeta nova anomenada «Components» al directori src i afegeix un fitxer Modal.js. Després, afegeix el codi següent:

Modal.js
import React, { Component } from "react";
 
// importing all of these classes from reactstrap module
import {
  Button,
  Modal,
  ModalHeader,
  ModalBody,
  ModalFooter,
  Form,
  FormGroup,
  Input,
  Label
} from "reactstrap";
 
class CustomModal extends Component {
  constructor(props) {
    super(props);
    this.state = {
      activeItem: this.props.activeItem
    };
  }
  // changes handler to check if a checkbox is checked or not
  handleChange = e => {
    let { name, value } = e.target;
    if (e.target.type === "checkbox") {
      value = e.target.checked;
    }
    const activeItem = { ...this.state.activeItem, [name]: value };
    this.setState({ activeItem });
  };
 
  // rendering modal in the custommodal class received toggle and on save as props,
  render() {
    const { toggle, onSave } = this.props;
    return (
      <Modal isOpen={true} toggle={toggle}>
        <ModalHeader toggle={toggle}> Customer Item </ModalHeader>
        <ModalBody>
 
          <Form>
 
            {/* 3 formgroups
            1 title label */}
            <FormGroup>
              <Label for="title">Customer ID</Label>
              <Input
                type="text"
                name="title"
                value={this.state.activeItem.title}
                onChange={this.handleChange}
                placeholder="Enter Customer ID"
              />
            </FormGroup>
 
            {/* 2 description label */}
            <FormGroup>
              <Label for="description">Customer name</Label>
              <Input
                type="text"
                name="description"
                value={this.state.activeItem.description}
                onChange={this.handleChange}
                placeholder="Enter Customer Name"
              />
            </FormGroup>
 
            {/* 3 completed label */}
            <FormGroup check>
              <Label for="completed">
                <Input
                  type="checkbox"
                  name="completed"
                  checked={this.state.activeItem.completed}
                  onChange={this.handleChange}
                />
                Completed
              </Label>
            </FormGroup>
          </Form>
        </ModalBody>
        {/* create a modal footer */}
        <ModalFooter>
          <Button color="success" onClick={() => onSave(this.state.activeItem)}>
            Save
          </Button>
        </ModalFooter>
      </Modal>
    );
  }
}
export default CustomModal

8. Fes els canvis indicats a continuació al fitxer index.js:

index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
 
import "bootstrap/dist/css/bootstrap.min.css";
 
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);

9. Per fer les peticions als endpoints de l'API al servidor del framework de Django, necessitarem instal·lar l'Axios. Utilitza l'ordre següent dins de la carpeta del framework per a instal·lar l'Axios:

npm install axios

I, amb aquest pas, ja tenim una aplicació Fullstack Django-React utilitzant el framework REST Django per establir la comunicació entre el frontend i el backend.

Exercici: Aplica la serialització de manera que es puguin gestionar els comptes associats als clients.