Ací es mostren les diferències entre la revisió seleccionada i la versió actual de la pàgina.
| Ambdós costats versió prèvia Revisió prèvia Següent revisió | Revisió prèvia | ||
|
django_react [2026/04/09 22:41] enric_mieza_sanchez [Projecte Django] |
django_react [2026/04/14 18:26] (actual) enric_mieza_sanchez [Django-Environ i el VCS] |
||
|---|---|---|---|
| Línia 135: | Línia 135: | ||
| from django.shortcuts import get_object_or_404 | from django.shortcuts import get_object_or_404 | ||
| from typing import List, Optional, Union, Literal | from typing import List, Optional, Union, Literal | ||
| + | import datetime | ||
| from .models import * | from .models import * | ||
| Línia 152: | Línia 153: | ||
| qs = Llibre.objects.all() | qs = Llibre.objects.all() | ||
| return qs | return qs | ||
| - | |||
| </ | </ | ||
| Línia 163: | Línia 163: | ||
| ==== Django-Environ i el VCS ==== | ==== Django-Environ i el VCS ==== | ||
| - | Django Environ és un //plugin// que ens ajuda a la posada en producció. Particularment ens facilitarà configurar de forma segura les credencials per a la BD i altres serveis (social login, seguretat, tokens, accés a APIs externes, etc.). | + | [[https:// |
| Habitualment les configuracions es posen a l' | Habitualment les configuracions es posen a l' | ||
| Línia 181: | Línia 181: | ||
| </ | </ | ||
| + | Com podeu veure, hi ha l' | ||
| + | Si mirem el [[https:// | ||
| + | |||
| + | <file python settings.py> | ||
| + | import environ | ||
| + | import os | ||
| + | |||
| + | env = environ.Env( | ||
| + | DEBUG=(bool, | ||
| + | ) | ||
| + | |||
| + | # Podeu deixar les instruccions que hi hagi de l' | ||
| + | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | ||
| + | |||
| + | # llegim .env | ||
| + | environ.Env.read_env(os.path.join(BASE_DIR, | ||
| + | |||
| + | # variables a llegir de .env | ||
| + | # ULL! cal elimninar les variables que hi ha al settings.py | ||
| + | DEBUG = env(' | ||
| + | SECRET_KEY = env(' | ||
| + | ALLOWED_HOSTS = env.list(" | ||
| + | DATABASES = { | ||
| + | # configura a través de la variable DATABASE_URL | ||
| + | ' | ||
| + | } | ||
| + | # dominis amb autorització per a fer crides a l'API | ||
| + | CORS_ALLOWED_ORIGINS = env.list(" | ||
| + | " | ||
| + | " | ||
| + | ]) | ||
| + | # protecció CSRF per atacs de dominis creuats | ||
| + | CSRF_TRUSTED_ORIGINS = env.list(" | ||
| + | </ | ||
| + | |||
| + | I després crearem '' | ||
| + | <file bash .env> | ||
| + | DEBUG=on | ||
| + | SECRET_KEY=your-secret-key | ||
| + | # | ||
| + | DATABASE_URL=sqlite:/// | ||
| + | ALLOWED_HOSTS=*, | ||
| + | CORS_ALLOWED_ORIGINS=http:// | ||
| + | CSRF_TRUSTED_ORIGINS=http:// | ||
| + | </ | ||
| + | |||
| + | Sempre convé deixar aquest exemple mateix guardat com a '' | ||
| \\ | \\ | ||
| Línia 188: | Línia 235: | ||
| Creació del projecte React dins la carpeta '' | Creació del projecte React dins la carpeta '' | ||
| + | |||
| + | Ens hem de posar a la carpeta principal del projecte Django, al mateix nivell que el '' | ||
| $ npm create vite@latest react | $ npm create vite@latest react | ||
| - | Ja podem començar a fer codi! | + | |
| + | ==== Codi de l' | ||
| + | |||
| + | Els arxius de codi seran: | ||
| + | * App.jsx | ||
| + | * config.js | ||
| + | * services/ | ||
| + | * components/ | ||
| + | * components/ | ||
| + | |||
| + | I els CSS: | ||
| + | * App.css | ||
| + | * Modal.css | ||
| + | * styles.css | ||
| + | * index.css | ||
| + | |||
| + | |||
| + | === Javascript i JSX === | ||
| + | |||
| + | --> Proposta de codi JSX | ||
| + | |||
| + | <file javascript App.jsx> | ||
| + | import { useState } from ' | ||
| + | import ' | ||
| + | import BookList from ' | ||
| + | import ' | ||
| + | import ' | ||
| + | |||
| + | function App() { | ||
| + | return ( | ||
| + | <div className=" | ||
| + | < | ||
| + | </ | ||
| + | ); | ||
| + | } | ||
| + | |||
| + | export default App; | ||
| + | </ | ||
| + | |||
| + | <file javascript config.js> | ||
| + | const config = { | ||
| + | development: | ||
| + | API_URL: ' | ||
| + | DEBUG: true, | ||
| + | }, | ||
| + | production: { | ||
| + | API_URL: ' | ||
| + | DEBUG: false, | ||
| + | } | ||
| + | }; | ||
| + | |||
| + | const env = process.env.NODE_ENV || ' | ||
| + | export default config[env]; | ||
| + | |||
| + | </ | ||
| + | |||
| + | <file javascript services/ | ||
| + | import config from ' | ||
| + | |||
| + | const API_BASE_URL = config.API_URL; | ||
| + | |||
| + | export const getBooks = () => { | ||
| + | console.log(' | ||
| + | return fetch(API_BASE_URL+"/ | ||
| + | .then((response) => { | ||
| + | if (!response.ok) { | ||
| + | throw new Error(" | ||
| + | } | ||
| + | return response.json(); | ||
| + | }) | ||
| + | .catch((error) => { | ||
| + | console.error(' | ||
| + | return []; | ||
| + | }); | ||
| + | }; | ||
| + | </ | ||
| + | |||
| + | <file javascript components/ | ||
| + | import { useEffect, useState } from ' | ||
| + | import { getBooks } from ' | ||
| + | import BookItem from ' | ||
| + | import imgReact from ' | ||
| + | |||
| + | function BookList() { | ||
| + | const [books, setBooks] = useState([]); | ||
| + | |||
| + | useEffect(() => { | ||
| + | getBooks().then((data) => setBooks(data)); | ||
| + | }, []); | ||
| + | |||
| + | return ( | ||
| + | <div className=" | ||
| + | <img src={imgReact} /> | ||
| + | < | ||
| + | {books.length > 0 ? ( | ||
| + | books.map((book) => < | ||
| + | ) : ( | ||
| + | < | ||
| + | )} | ||
| + | </ | ||
| + | ); | ||
| + | } | ||
| + | |||
| + | export default BookList; | ||
| + | </ | ||
| + | |||
| + | <file javascript components/ | ||
| + | import React, { useState } from ' | ||
| + | |||
| + | function BookItem({ book }) { | ||
| + | const [modalObert, | ||
| + | |||
| + | function mostraDetalls() { | ||
| + | setModalObert(true); | ||
| + | } | ||
| + | |||
| + | function tancaModal() { | ||
| + | setModalObert(false); | ||
| + | } | ||
| + | |||
| + | return ( | ||
| + | <div className=" | ||
| + | < | ||
| + | <p> | ||
| + | < | ||
| + | </ | ||
| + | <button onClick={mostraDetalls}> | ||
| + | |||
| + | {/* Modal */} | ||
| + | {modalObert && ( | ||
| + | <div className=" | ||
| + | <div className=" | ||
| + | <div className=" | ||
| + | < | ||
| + | <button className=" | ||
| + | </ | ||
| + | <div className=" | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | {/* Afegeix més camps segons les propietats del teu objecte book */} | ||
| + | </ | ||
| + | <div className=" | ||
| + | <button onClick={tancaModal}> | ||
| + | </ | ||
| + | </ | ||
| + | </ | ||
| + | )} | ||
| + | </ | ||
| + | ); | ||
| + | } | ||
| + | |||
| + | export default BookItem; | ||
| + | </ | ||
| + | |||
| + | <-- | ||
| \\ | \\ | ||
| + | === Estils CSS === | ||
| + | |||
| + | --> Proposta de codi CSS | ||
| + | |||
| + | <file css App.css> | ||
| + | #root { | ||
| + | max-width: 1280px; | ||
| + | margin: 0 auto; | ||
| + | padding: 2rem; | ||
| + | text-align: center; | ||
| + | } | ||
| + | |||
| + | .logo { | ||
| + | height: 6em; | ||
| + | padding: 1.5em; | ||
| + | will-change: | ||
| + | transition: filter 300ms; | ||
| + | } | ||
| + | .logo:hover { | ||
| + | filter: drop-shadow(0 0 2em #646cffaa); | ||
| + | } | ||
| + | .logo.react: | ||
| + | filter: drop-shadow(0 0 2em #61dafbaa); | ||
| + | } | ||
| + | |||
| + | @keyframes logo-spin { | ||
| + | from { | ||
| + | transform: rotate(0deg); | ||
| + | } | ||
| + | to { | ||
| + | transform: rotate(360deg); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | @media (prefers-reduced-motion: | ||
| + | a: | ||
| + | animation: logo-spin infinite 20s linear; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | .card { | ||
| + | padding: 2em; | ||
| + | } | ||
| + | |||
| + | .read-the-docs { | ||
| + | color: #888; | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | <file css Modal.css> | ||
| + | /* Modal.css */ | ||
| + | .modal-overlay { | ||
| + | position: fixed; | ||
| + | top: 0; | ||
| + | left: 0; | ||
| + | right: 0; | ||
| + | bottom: 0; | ||
| + | background-color: | ||
| + | display: flex; | ||
| + | justify-content: | ||
| + | align-items: | ||
| + | z-index: 1000; | ||
| + | } | ||
| + | |||
| + | .modal-content { | ||
| + | background-color: | ||
| + | padding: 20px; | ||
| + | border-radius: | ||
| + | max-width: 500px; | ||
| + | width: 90%; | ||
| + | max-height: 80vh; | ||
| + | overflow-y: auto; | ||
| + | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); | ||
| + | } | ||
| + | |||
| + | .modal-header { | ||
| + | display: flex; | ||
| + | justify-content: | ||
| + | align-items: | ||
| + | margin-bottom: | ||
| + | border-bottom: | ||
| + | padding-bottom: | ||
| + | } | ||
| + | |||
| + | .modal-header h2 { | ||
| + | margin: 0; | ||
| + | font-size: 1.5rem; | ||
| + | } | ||
| + | |||
| + | .close-btn { | ||
| + | background: none; | ||
| + | border: none; | ||
| + | font-size: 24px; | ||
| + | cursor: pointer; | ||
| + | color: #666; | ||
| + | padding: 0; | ||
| + | width: 30px; | ||
| + | height: 30px; | ||
| + | display: flex; | ||
| + | align-items: | ||
| + | justify-content: | ||
| + | } | ||
| + | |||
| + | .close-btn: | ||
| + | color: #000; | ||
| + | } | ||
| + | |||
| + | .modal-body { | ||
| + | margin-bottom: | ||
| + | line-height: | ||
| + | } | ||
| + | |||
| + | .modal-body p { | ||
| + | margin: 10px 0; | ||
| + | } | ||
| + | |||
| + | .modal-footer { | ||
| + | display: flex; | ||
| + | justify-content: | ||
| + | border-top: 1px solid #eee; | ||
| + | padding-top: | ||
| + | } | ||
| + | |||
| + | .modal-footer button { | ||
| + | padding: 8px 16px; | ||
| + | background-color: | ||
| + | color: white; | ||
| + | border: none; | ||
| + | border-radius: | ||
| + | cursor: pointer; | ||
| + | } | ||
| + | |||
| + | .modal-footer button: | ||
| + | background-color: | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | <file css styles.css> | ||
| + | body { | ||
| + | font-family: | ||
| + | background-color: | ||
| + | text-align: center; | ||
| + | } | ||
| + | |||
| + | .container { | ||
| + | max-width: 800px; | ||
| + | margin: 20px auto; | ||
| + | } | ||
| + | |||
| + | .book-card { | ||
| + | background: white; | ||
| + | padding: 15px; | ||
| + | border-radius: | ||
| + | box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); | ||
| + | margin-bottom: | ||
| + | text-align: left; | ||
| + | } | ||
| + | |||
| + | h1 { | ||
| + | color: #333; | ||
| + | } | ||
| + | |||
| + | h3 { | ||
| + | color: #007bff; | ||
| + | } | ||
| + | |||
| + | h4 { | ||
| + | margin-top: 10px; | ||
| + | } | ||
| + | |||
| + | ul { | ||
| + | list-style: none; | ||
| + | padding: 0; | ||
| + | } | ||
| + | |||
| + | ul li { | ||
| + | background: #e9ecef; | ||
| + | padding: 5px; | ||
| + | margin: 5px 0; | ||
| + | border-radius: | ||
| + | } | ||
| + | |||
| + | p { | ||
| + | color: black; | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Aquest sol venir a l' | ||
| + | <file css index.css> | ||
| + | :root { | ||
| + | font-family: | ||
| + | line-height: | ||
| + | font-weight: | ||
| + | |||
| + | color-scheme: | ||
| + | color: rgba(255, 255, 255, 0.87); | ||
| + | background-color: | ||
| + | |||
| + | font-synthesis: | ||
| + | text-rendering: | ||
| + | -webkit-font-smoothing: | ||
| + | -moz-osx-font-smoothing: | ||
| + | } | ||
| + | |||
| + | a { | ||
| + | font-weight: | ||
| + | color: #646cff; | ||
| + | text-decoration: | ||
| + | } | ||
| + | a:hover { | ||
| + | color: #535bf2; | ||
| + | } | ||
| + | |||
| + | body { | ||
| + | margin: 0; | ||
| + | display: flex; | ||
| + | place-items: | ||
| + | min-width: 320px; | ||
| + | min-height: 100vh; | ||
| + | } | ||
| + | |||
| + | h1 { | ||
| + | font-size: 3.2em; | ||
| + | line-height: | ||
| + | } | ||
| + | |||
| + | button { | ||
| + | border-radius: | ||
| + | border: 1px solid transparent; | ||
| + | padding: 0.6em 1.2em; | ||
| + | font-size: 1em; | ||
| + | font-weight: | ||
| + | font-family: | ||
| + | background-color: | ||
| + | cursor: pointer; | ||
| + | transition: border-color 0.25s; | ||
| + | } | ||
| + | button: | ||
| + | border-color: | ||
| + | } | ||
| + | button: | ||
| + | button: | ||
| + | outline: 4px auto -webkit-focus-ring-color; | ||
| + | } | ||
| + | |||
| + | @media (prefers-color-scheme: | ||
| + | :root { | ||
| + | color: #213547; | ||
| + | background-color: | ||
| + | } | ||
| + | a:hover { | ||
| + | color: #747bff; | ||
| + | } | ||
| + | button { | ||
| + | background-color: | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | <-- | ||
| + | |||
| + | \\ | ||