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:
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:
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:
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ó:
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:
# 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ó:
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:
# 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:
# 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:
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):
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:
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:
// 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:
.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:
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:
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.