article

Showing posts with label Python-Flask. Show all posts
Showing posts with label Python-Flask. Show all posts

Monday, March 10, 2025

Reactjs 18 Python Flask CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS

Reactjs 18 Python Flask CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS

react.dev
Create react project
Run the following command in your terminal: vite_dev/guide/
npm create vite@latest
tanstack/react-query
Hooks for fetching, caching and updating asynchronous data in React, Solid, Svelte and Vue

npm i @tanstack/react-query

react-query-devtools
npm i @tanstack/react-query-devtools

Tailwind CSS with Vite
tailwindcss_com/docs/guides/vite

Install npm i uuid

my-app\src\App.jsx
//src\App.jsx
import { Route, Routes } from "react-router-dom" //npm i react-router-dom
import Home from "./pages/Home"
import Create from "./pages/Create"
import Read from "./pages/Read"
import EditCustomer from "./pages/EditCustomer"

function App() { 
  return (
    <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/create/" element={<Create />} />
        <Route path="/read/:id" element={<Read />} />
        <Route path="/edit/:id" element={<EditCustomer />} />
    </Routes>
  )
}
 
export default App
my-app\src\main.jsx
//src\main.jsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from '@tanstack/react-query-devtools' //npm i @tanstack/react-query-devtools npmjs com/package/@tanstack/react-query-devtools
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import { BrowserRouter } from "react-router-dom";
import App from './App.jsx'
 
// create a client
const queryClient = new QueryClient();
 
createRoot(document.getElementById("root")).render(
  <StrictMode>
    <BrowserRouter>
      <QueryClientProvider client={queryClient}>
        <App />
        <ReactQueryDevtools initialIsOpen={false} />
      </QueryClientProvider>
    </BrowserRouter>
  </StrictMode>,
);
Home Page : my-app\src\pages\Home.jsx
//src\pages\Home.jsx
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query tanstack com/query/latest/docs/framework/react/installation
import { getCustomerList } from "../api/customer";
import Pagination from "../components/Pagination";
import { useSearchParams } from "react-router-dom" 
import CustomerList from "../pages/CustomerList";

export default function Home() {
    const [searchParams] = useSearchParams();
    const page = Number(searchParams.get("page"));
    //console.log(page)
 
    const currentPage = Number(page) || 1;
    //const currentPage = 1;
 
    const { isLoading, data, isError,  error } = useQuery({
        queryKey: ["customers", currentPage],
        queryFn: () => getCustomerList(currentPage)
    });
 
    //console.log(data);  
    if (isLoading) return "Loading...";
    if (isError) return `Error: ${error.message}`;
 
    const totalPages = Math.ceil(Number(data.totalpage) / Number(data.perpage));
    //console.log(totalPages);
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="flex items-center justify-between gap-1 mb-5 pl-10 pr-10">
            <h1 className="text-4xl font-bold">Reactjs 18 Python Flask CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS</h1>
        </div> 
        <div className="overflow-x-auto pt-10">
            <div className="mb-2 w-full text-right">
                <a
                href="/create"
                className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">
                Add New
                </a>
            </div>
            <CustomerList customerlist={data.customerlist} />
            <div className="flex items-center justify-between my-5">
                <Pagination totalPages={totalPages}/>
            </div>
        </div>
    </div>
  );
}
Customer List page : my-app\src\pages\CustomerList.jsx
//my-app\src\pages\CustomerList.jsx
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; //npm i @tanstack/react-query 
import { deleteCustomer } from "../api/customer";

const CustomerList = ({ customerlist }) => { 
    const queryClient = useQueryClient();

    const deleteCustomerMutation = useMutation({
      mutationFn: deleteCustomer,
      onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['customers']});
      }
    });
 
    const handleDelete = (id) => {
      deleteCustomerMutation.mutate(id)
    }

  return (
    <>
        <table className="table table-zebra">
            <thead className="text-sm text-gray-700 uppercase bg-gray-50">
                <tr>
                    <th className="py-3 px-6">ID</th>
                    <th className="py-3 px-6">Name</th>
                    <th className="py-3 px-6">Email</th>
                    <th className="py-3 px-6 text-center">Actions</th>
                </tr>
            </thead>
            <tbody>
                {customerlist.map((item) => (
                    <tr key={item.id} className="bg-white border-b text-black">
                        <td className="py-3 px-6">
                        {item.id}
                        </td>
                        <td className="py-3 px-6">{item.name}</td>   
                        <td className="py-3 px-6">{item.email}</td>
                        <td className="flex justify-center gap-1 py-3">
                            <a className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
                                href={`/read/${item.id}`}>Read</a> 
                              
                            <a className="focus:outline-none text-white bg-yellow-400 hover:bg-yellow-500 focus:ring-4 focus:ring-yellow-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:focus:ring-yellow-900"
                                href={`/edit/${item.id}/`}>
                                Edit
                            </a>
                            <button onClick={() => handleDelete(item.id)}   className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900">
                                Delete</button>
                        </td>
                    </tr>
                ))}
            </tbody>
        </table>
    </>
  );
};

export default CustomerList;
Create page : my-app\src\pages\Create.jsx
//my-app\src\pages\Create.jsx
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { createCustomer } from "../api/customer"
import UserForm from "../components/UserForm"
import {useNavigate} from 'react-router-dom'

const Create = () => {
  const queryClient = useQueryClient();
 
  const navigate = useNavigate()

  const createCustomerMutation = useMutation({
    mutationFn: createCustomer,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['customers']});
      console.log("success!");
      navigate('/')
    }
  });
 
  const handleAddPost = (customer) => {
    createCustomerMutation.mutate({
      ...customer
    })
  }
 
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="max-w-md mx-auto mt-5">
        <h1 className="text-2xl text-center mb-2">Add New Customer</h1>
        <UserForm onSubmit={handleAddPost} initialValue={{}} />
        </div>
    </div>
  )
}
 
export default Create
Read page : my-app\src\pages\Read.jsx
//my-app\src\pages\Read.jsx
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { useNavigate, useParams } from "react-router-dom";
import { fetchCustomer } from "../api/customer";
 
const Read = () => {
    const navigate = useNavigate();
    const { id } = useParams();
    //console.log(id);
    const {
        isLoading,
        isError,
        data: customer,
        error,
    } = useQuery({
        queryKey: ["customers", id],
        queryFn: () => fetchCustomer(id),
    });
    
    if (isLoading) return "loading...";
    if (isError) return `Error: ${error.message}`;
 
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="overflow-x-auto py-10">
        <button className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
            onClick={() => navigate("/")}>back to list customer</button>
        <h1>{customer.getcustomer.customer.name}</h1>
        <p>{customer.getcustomer.customer.email}</p>
        </div>
    </div>
  )
}
 
export default Read
Read page : my-app\src\pages\EditCustomer.jsx
//my-app\src\pages\EditCustomer.jsx
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useNavigate, useParams } from "react-router-dom";
import { fetchCustomer, updateCustomer } from "../api/customer";
import UserForm from "../components/UserForm"
 
const EditCustomer = () => {
    const queryClient = useQueryClient();
    const navigate = useNavigate();
    const { id } = useParams();

    const {
        isLoading,
        isError,
        data: customer,
        error,
    } = useQuery({
        queryKey: ["customers", id],
        queryFn: () => fetchCustomer(id),
    });
    //console.log(customer);

    const updateUserMutation = useMutation({
        mutationFn: updateCustomer,
        onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['customers']});
        navigate("/")
        }
    })
    
    if (isLoading) return "loading...";
    if (isError) return `Error: ${error.message}`;
    
    const handleSubmit = (updatedCustomer) => {
        updateUserMutation.mutate({id, ...updatedCustomer})
    }
    
 
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="overflow-x-auto py-10">
            <h1>{customer.getcustomer.name}</h1>
            <UserForm onSubmit={handleSubmit} initialValue={customer.getcustomer.customer}  />
        </div>
    </div>
  )
}
 
export default EditCustomer
Form : my-app\src\components\UserForm.jsx
//src\components\UserForm.jsx
import { useState } from "react"
 
const UserForm = ({ onSubmit, initialValue }) => {
  const [user, setUser] = useState({
    name: initialValue.name || "",
    email: initialValue.email || ""
  });
 
  const handleChangeInput = (e) => {
    setUser({
      ...user,
      [e.target.name]: e.target.value
    })
  }
 
  const renderField = (label) => (
    <div>
      <label className="block text-sm font-medium text-gray-900">{label}</label>
      <input onChange={handleChangeInput} type="text" name={label.toLowerCase()} value={user[label.toLowerCase()]} 
        className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500blue-500"
      />
    </div>
  );
 
  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(user);
    setUser({
      name: "",
      email: ""
    })
 
  }
 
  return (
    <form onSubmit={handleSubmit}>
      <div className="mb-5">{renderField('Name')}</div>
      <div className="mb-5">{renderField('Email')}</div>
      <button type="submit"
        className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
      >
        Submit</button>
    </form>
  )
}
 
export default UserForm
Api : my-app\src\api\customer.jsx
//src\api\customer.jsx
export async function getCustomerList(page) {
  const response = await fetch(`http://127.0.0.1:5000/api/customerpagination/${page}/3`);
  //const response = await fetch("http://127.0.0.1:5000/api/customer/");
  const data = await response.json();
  //console.log(data);
  return {
    customerlist: data.customers,
    totalpage: data.total, 
    perpage: data.per_page
  }
}

export async function fetchCustomer(id) {
  const response = await fetch("http://127.0.0.1:5000/api/customer/"+id);
  const data = await response.json();
  console.log(data);
  return {
    getcustomer: data
  }
}

export async function createCustomer(newCustomer) {
  const response = await fetch("http://127.0.0.1:5000/api/customer", {
    method: "POST",
    headers: { 
      "Content-Type": "application/json"
    },
    body: JSON.stringify(newCustomer)
  });
  console.log(response);
  return response.json()
}

export async function updateCustomer(updatedCustomer) {
  const response = await fetch(`http://127.0.0.1:5000/api/customer/${updatedCustomer.id}`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(updatedCustomer)
  });
  console.log(response)
  return response.json()
}

export async function deleteCustomer(id) {
  const response = await fetch(`http://127.0.0.1:5000/api/customer/${id}`, {
    method: "DELETE",
  });
  return response.json()
}
Pagination : my-app\src\components\Pagination.jsx
//src\components\Pagination.jsx
import { HiChevronLeft, HiChevronRight } from "react-icons/hi"; //npm install react-icons --save npmjs com/package/react-icons
import clsx from "clsx"; //npm i clsx npmjs com/package/clsx
import { useSearchParams, useLocation } from "react-router-dom" 

export const generatePagination = (currentPage, totalPages) => {
  if (totalPages <= 7) {
    return Array.from({ length: totalPages }, (_, i) => i + 1);
  }
   
  if (currentPage <= 3) {
    return [1, 2, 3, "...", totalPages - 1, totalPages];
  }
  
  if (currentPage >= totalPages - 2) {
    return [1, 2, 3, "...", totalPages - 2, totalPages - 1, totalPages];
  }
  
  return [
    1,
    "...",
    currentPage - 1,
    currentPage,
    currentPage + 1,
    "...",
    totalPages,
  ];
};

const Paginationnumber = ({ totalPages }) => {
  let location = useLocation(); 
  console.log(location);
  const { hash, pathname, search } = location;
  //console.log(pathname);

  const [searchParams] = useSearchParams();
  const currentPage = Number(searchParams.get("page")) || 1;
  //console.log(currentPage); 2

  const allPages = generatePagination(currentPage, totalPages);

  const createPageURL = (pageNumber) => {
      const params = searchParams;
      params.set("page", pageNumber.toString());
      return `${pathname}?${params.toString()}`;
  };

  const PaginationNumber = ({
      page,
      href,
      position,
      isActive,
  }) => {
      const className = clsx(
          "flex h-10 w-10 items-center justify-center text-sm border",
          {
              "rounded-l-sm": position === "first" || position === "single",
              "rounded-r-sm": position === "last" || position === "single",
              "z-10 bg-blue-700 border-blue-500 text-white bg-blue-700": isActive,
              "hover:bg-blue-700": !isActive && position !== "middle",
              "text-gray-300 pointer-events-none": position === "middle",
          }
      );

      return isActive && position === "middle" ? (
          <div className={className}>{page}</div>
      ) : (
          <a href={href} className={className}>
              {page}
          </a>
      );
  };

  const PaginationArrow = ({ href, direction, isDisabled }) => {
      const className = clsx(
          "flex h-10 w-10 items-center justify-center text-sm border",
          {
              "pointer-events-none text-blue-300": isDisabled,
              "hover:bg-blue-700": !isDisabled,
              "mr-2": direction === "left",
              "ml-2": direction === "right",
          }
      );

      const icon =
        direction === "left" ? (
          <HiChevronLeft size={20} />
        ) : (
          <HiChevronRight size={20} />
        );

    return isDisabled ? (
      <div className={className}>{icon}</div>
    ) : (
      <a href={href} className={className}>
        {icon}
      </a>
    );
  };

  return (
    <div className="inline-flex">
      <PaginationArrow
        direction="left"
        href={createPageURL(currentPage - 1)}
        isDisabled={currentPage <= 1}
      />
      <div className="flex -space-x-px">
        {allPages.map((page, index) => {
          let position;

          if (index === 0) position = "first";
          if (index === allPages.length - 1) position = "last";
          if (allPages.length === 1) position = "single";
          if (page === "...") position = "middle";

          return (
            <PaginationNumber
              key={index}
              href={createPageURL(page)}
              page={page}
              position={position}
              isActive={currentPage === page} 
            />
          );
        })}
      </div>
      <PaginationArrow
        direction="right"
        href={createPageURL(currentPage + 1)}
        isDisabled={currentPage >= totalPages}
      />
    </div>
  );
};

export default Paginationnumber;
Run
C:\react-js\my-app> npm run dev

python-guide-es.readthedocs.io/es/guide-es/dev/virtualenvs.html

Create an environment
ednalan@Cairocoders myapp % pip install virtualenv
ednalan@Cairocoders myapp % pip install virtualenv

Activate the environment
ednalan@Cairocoders myapp % source venv/bin/activate
(venv) ednalan@Cairocoders myapp %

Install Flask
pypi.org/project/Flask/
(venv) ednalan@Cairocoders myapp % pip install -U Flask
(venv) ednalan@Cairocoders myapp % flask run

Install requirements
pip install -U flask-cors
pypi.org/project/Flask-Cors/

Flask-SQLAlchemy
Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application.
flask-sqlalchemy.palletsprojects.com/en/3.0.x/

(venv) PS C:\flask_dev\myapp>pip install -U Flask-SQLAlchemy

Flask + marshmallow for beautiful APIs
pypi.org/project/flask-marshmallow/

(venv) PS C:\flask_dev\myapp>pip install flask-marshmallow

python3 -m pip install
https://pypi.org/project/pymysql/
app.py
 
//app.py
from flask import Flask, request, jsonify, make_response
from flask_cors import CORS, cross_origin #ModuleNotFoundError: No module named 'flask_cors' = pip install Flask-Cors
from models import db, Customer
 
app = Flask(__name__)
 
app.config['SECRET_KEY'] = 'cairocoders-ednalan'
#app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///flaskdb.db'
# Databse configuration mysql                             Username:password@hostname/databasename
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:root@localhost:8889/reactjsdb'
   
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = True
   
CORS(app, supports_credentials=True)

db.init_app(app)
      
with app.app_context():
    db.create_all()

@app.route("/")
def hello():
    return "Hello, World!"

# create a test route
@app.route('/test', methods=['GET'])
def test():
    return jsonify({'message': 'The server is running'})

# create a customer
@app.route('/api/customer', methods=['POST'])
def create_customer():
    try:
        data = request.get_json()
        new_customer = Customer(name=data['name'], email=data['email'])
        db.session.add(new_customer)
        db.session.commit()  
 
        return jsonify({
            'id': new_customer.id,
            'name': new_customer.name,
            'email': new_customer.email
        }), 201 
 
    except Exception as e:
        return make_response(jsonify({'message': 'error creating customer', 'error': str(e)}), 500)
    
# get all customer
@app.route('/api/customer', methods=['GET'])
def get_customer():
    try:
        customer = Customer.query.all()
        customer_data = [{'id': customer.id, 'name': customer.name, 'email': customer.email} for customer in customer]
        return jsonify(customer_data), 200
    except Exception as e:
        return make_response(jsonify({'message': 'error getting customer', 'error': str(e)}), 500)

@app.route('/api/customerpagination/<int:page>/<int:per_page>', methods=['GET'])
def customerpagination(page=1, per_page=3):
  
    total = Customer.query.count()
    print(total)
  
    customers = Customer.query.order_by(Customer.id.asc())  
    customers = customers.paginate(page=page, per_page=per_page)
  
    return jsonify({
        'total': total,
        'page': page,
        'per_page': per_page,
        'has_next': customers.has_next,
        'has_prev': customers.has_prev,
        'page_list': [iter_page if iter_page else '...' for iter_page in customers.iter_pages()],
        'customers': [{
            'id': p.id,
            'name': p.name,
            'email': p.email
        } for p in customers.items]
    })
    
# get a customers by id
@app.route('/api/customer/<id>', methods=['GET'])
def get_customerid(id):
    try:
        customer = Customer.query.filter_by(id=id).first() # get the first customer with the id
        if customer:
            return make_response(jsonify({'customer': customer.json()}), 200)
        return make_response(jsonify({'message': 'customer not found'}), 404) 
    except Exception as e:
        return make_response(jsonify({'message': 'error getting customer', 'error': str(e)}), 500)

# update a customer by id
@app.route('/api/customer/<id>', methods=['PUT'])
def update_customer(id):
    try:
        customer = Customer.query.filter_by(id=id).first()
        if customer:
            data = request.get_json()
            customer.name = data['name']
            customer.email = data['email']
            db.session.commit()
            return make_response(jsonify({'message': 'customer updated'}), 200)
        return make_response(jsonify({'message': 'customer not found'}), 404)  
    except Exception as e:
        return make_response(jsonify({'message': 'error updating customer', 'error': str(e)}), 500)

# delete a customer by id
@app.route('/api/customer/<id>', methods=['DELETE'])
def delete_customer(id):
    try:
        customer = Customer.query.filter_by(id=id).first()
        if customer:
            db.session.delete(customer)
            db.session.commit()
            return make_response(jsonify({'message': 'customer deleted'}), 200)
        return make_response(jsonify({'message': 'customer not found'}), 404) 
    except Exception as e:
        return make_response(jsonify({'message': 'error deleting customer', 'error': str(e)}), 500)  
    
if __name__ == "__main__":
    app.run(debug=True)
models.py
 
//models.py
from flask_sqlalchemy import SQLAlchemy
    
db = SQLAlchemy()
    
class Customer(db.Model):
    __tablename__ = 'customer'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

    def json(self):
        return {'id': self.id,'name': self.name, 'email': self.email}
run (venv) C:\flask_dev\myapp>flask run

Monday, February 24, 2025

Nextjs 15 Python Flask Rest API CRUD (Create, Read, Update and Delete) with Pagination Tanstack Query Tailwind CSS

Nextjs 15 Python Flask Rest API CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS

Install nextjs npx create-next-app@latest nextjs_org/docs/getting-started/installation

Install react-query

npm i @tanstack/react-query
tanstack com/query/latest/docs/framework/react/installation
app\page.tsx
//app\page.tsx
"use client";

import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query https://tanstack.com/query/latest/docs/framework/react/installation
import CustomerList from "./components/CustomerList";
import { getCustomerList } from "./api/customer";
import Paginationnumber from "./components/Paginationnumber";
import LoadingSpinner from "./components/LoadingSpinner";
import { useSearchParams } from 'next/navigation'
import Link from "next/link";

export default function Home() {
    const searchParams = useSearchParams() 
    const page = searchParams.get('page')
    //console.log(page)

    const currentPage = Number(page) || 1;

    const { isLoading, data, isError, isFetching,  error } = useQuery({
        queryKey: ["customers", currentPage],
        queryFn: () => getCustomerList(currentPage)
    });

    //console.log(data);
    if (isLoading) return "Loading...";
    if (isError) return `Error: ${error.message}`;

    const totalPages = Math.ceil(Number(data.totalpage) / Number(data.perpage));
    //console.log(totalPages);
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="flex items-center justify-between gap-1 mb-5 pl-10 pr-10">
            <h1 className="text-4xl font-bold">Nextjs 15 Python Flask Rest API CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS</h1>
        </div> 
        <div className="overflow-x-auto pt-10">
            <div className="mb-2 w-full text-right">
                <Link
                href="/customer/create"
                className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">
                Add New
                </Link>
            </div>
            <CustomerList customerlist={data.customerlist} />
            <div className="flex items-center justify-between my-5">
                <Paginationnumber totalPages={totalPages} />
                {isFetching ? <LoadingSpinner /> : null}
            </div>
        </div>
    </div>
  );
}
app\layout.tsx
//app\layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import QueryProvider from "./components/QueryProvider";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <QueryProvider>{children}</QueryProvider>
      </body>
    </html>
  );
}
app\components\QueryProvider.tsx
//app\components\QueryProvider.tsx
"use client";

import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { useState } from "react";

interface Props {
  children: React.ReactNode;
}

export default function QueryProvider({ children }: Props) {
  const [queryClient] = useState(() => new QueryClient());
  return (
    <QueryClientProvider client={queryClient}> {children} </QueryClientProvider>
  );
}
app\components\CustomerList.tsx
//app\components\CustomerList.tsx
import Link from "next/link";
import { useMutation, useQueryClient } from "@tanstack/react-query"; //npm i @tanstack/react-query 
import { deleteCustomer } from "../api/customer";

const CustomerList = ({ customerlist }) => { 
    const queryClient = useQueryClient();

    const deleteCustomerMutation = useMutation({
      mutationFn: deleteCustomer,
      onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['customers']});
      }
    });
 
    const handleDelete = (id) => {
        deleteCustomerMutation.mutate(id)
    }
  return (
    <>
        <table className="table table-zebra">
            <thead className="text-sm text-gray-700 uppercase bg-gray-50">
                <tr>
                    <th className="py-3 px-6">ID</th>
                    <th className="py-3 px-6">Name</th>
                    <th className="py-3 px-6">Email</th>
                    <th className="py-3 px-6">Contact No.</th>
                    <th className="py-3 px-6 text-center">Actions</th>
                </tr>
            </thead>
            <tbody>
                {customerlist.map((item) => (
                    <tr key={item.id} className="bg-white border-b text-black">
                        <td className="py-3 px-6">
                        {item.id}
                        </td>
                        <td className="py-3 px-6">{item.name}</td>   
                        <td className="py-3 px-6">{item.email}</td>
                        <td className="py-3 px-6">{item.contactnumber}</td>
                        <td className="flex justify-center gap-1 py-3">
                            <Link className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
                                href={`/customer/${item.id}`}>Read</Link> 
                             
                            <Link className="focus:outline-none text-white bg-yellow-400 hover:bg-yellow-500 focus:ring-4 focus:ring-yellow-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:focus:ring-yellow-900"
                                href={`/customer/edit/${item.id}/`}>
                                Edit
                            </Link>
                            <button onClick={() => handleDelete(item.id)}  className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900">
                                Delete</button>
                        </td>
                    </tr>
                ))}
            </tbody>
        </table>
    </>
  )
}
 
export default CustomerList
app\components\LoadingSpinner.tsx
//app\components\LoadingSpinner.tsx
export default function LoadingSpinner() {
    return (
        <h1>Loading...</h1>
    )
} 
app\customer\create\page.tsx
//app\customer\create\page.tsx
"use client";

import { useMutation, useQueryClient } from "@tanstack/react-query"
import CustomertForm from "../../components/CustomertForm"
import { createCustomer } from "../../api/customer"
import { useRouter } from 'next/navigation'

const AddCustomer= () => {
  const queryClient = useQueryClient();
  const router = useRouter();

  const createUserMutation = useMutation({
    mutationFn: createCustomer,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['customers']});
      //console.log("success!")
      router.push('/')
    }
  });
 
  const handleAddPost = (customer) => {
    //console.log(customer)
    createUserMutation.mutate({
      ...customer
    })
  }
   
  return (
    <div className="max-w-md mx-auto mt-5 mb-5">
      <h1 className="text-2xl text-center mb-2">Add New Customer</h1>
      <CustomertForm onSubmit={handleAddPost} initialValue={{}} />
    </div>
  )
}
  
export default AddCustomer
app\components\CustomertForm.tsx
//app\components\CustomertForm.tsx
"use client";

import { useState } from "react"
  
const CustomertForm = ({ onSubmit, initialValue }) => {
  const [customer, setCustomer] = useState({
    name: initialValue.name || "",
    email: initialValue.email || "",
    contactnumber: initialValue.contactnumber || ""
  });
  //console.log(customer);
  const handleChangeInput = (e) => {
    setCustomer({
      ...customer,
      [e.target.name]: e.target.value
    })
  }
  
  const renderField = (label) => (
    <div>
      <label className="block text-sm font-medium text-gray-900">{label}</label>
      <input onChange={handleChangeInput} type="text" name={label.toLowerCase()} value={customer[label.toLowerCase()]} 
        className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500blue-500"
      />
    </div>
  );
  
  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(customer);
    setCustomer({
      name: "",
      email: "",
      contactnumber: "",
    })
  
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <div className="mb-5">{renderField('Name')}</div>
      <div className="mb-5">{renderField('Email')}</div>
      <div className="mb-5">{renderField('Contactnumber')}</div>
      <button type="submit"
        className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
      >
        Submit</button>
    </form>
  )
}
  
export default CustomertForm
app\customer\[id]\page.tsx
//app\customer\[id]\page.tsx
"use client";
 
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { useParams } from 'next/navigation'
import { fetchCustomer } from "../../api/customer";

const ViewCustomer = () => {
 
  const {id}=useParams();
 
  const {
    isLoading,
    isError,
    data: customer,
    error,
  } = useQuery({
    queryKey: ["customer", id],
    queryFn: () => fetchCustomer(id),
  });
  //console.log(customer);
  if (isLoading) return "loading...";
  if (isError) return `Error: ${error.message}`;
  
  //console.log(customer)
  return (
    <div className="min-h-screen flex items-center justify-center bg-slate-100">
      <div className="bg-white rounded-sm shadow p-8 text-black">
      <div className="max-w-2xl mx-auto mt-5">
        <h1 className="text-2xl text-center mb-2">View Customer</h1>
        <h2>ID : {customer.customer.id}</h2>
        <h2>Name : {customer.customer.name}</h2>
        <h2>Email : {customer.customer.email}</h2>
        <h2>Contact Number : {customer.customer.contactnumber}</h2>
      </div>
      </div>
    </div>
  )
}
  
export default ViewCustomer
app\customer\edit\[id]\page.tsx
//app\customer\edit\[id]\page.tsx
"use client";
 
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useParams, useRouter } from 'next/navigation'
import { fetchCustomer, updateCustomer } from "../../../api/customer";
import CustomertForm from "../../../components/CustomertForm"
 
const EditCustomer = () => {
  const {id}=useParams();
  //console.log(id);
 
  const queryClient = useQueryClient();
  const router = useRouter();
 
  const {
    isLoading,
    isError,
    data: customer,
    error,
  } = useQuery({
    queryKey: ["customers", id],
    queryFn: () => fetchCustomer(id),
  });
 
  console.log(customer);

  const updateCustomerMutation = useMutation({
    mutationFn: updateCustomer,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['customers']});
      console.log("success");
      router.push('/')
    }
  })
 
  if (isLoading) return "loading...";
  if (isError) return `Error: ${error.message}`;
 
  const handleSubmit = (updatedCustomer) => {
    updateCustomerMutation.mutate({id, ...updatedCustomer})
  }
 
  return (
    <div className="max-w-md mx-auto mt-5">
        <div className="flex items-center justify-between gap-1 mb-5">
            <h1 className="text-4xl font-bold">{customer.customer.name}</h1>
        </div> 
        <CustomertForm onSubmit={handleSubmit} initialValue={customer.customer} />
    </div>
  )
}
  
export default EditCustomer
app\components\Paginationnumber.tsx
//app\components\Paginationnumber.tsx
"use client";
 
import Link from "next/link";
import { HiChevronLeft, HiChevronRight } from "react-icons/hi"; //npm install react-icons --save npmjs com/package/react-icons
import { usePathname, useSearchParams } from "next/navigation";
import clsx from "clsx"; //npm i clsx npmjs com/package/clsx
 
export const generatePagination = (currentPage: number, totalPages: number) => {
  if (totalPages <= 7) {
    return Array.from({ length: totalPages }, (_, i) => i + 1);
  }
  
  if (currentPage <= 3) {
    return [1, 2, 3, "...", totalPages - 1, totalPages];
  }
 
  if (currentPage >= totalPages - 2) {
    return [1, 2, 3, "...", totalPages - 2, totalPages - 1, totalPages];
  }
 
  return [
    1,
    "...",
    currentPage - 1,
    currentPage,
    currentPage + 1,
    "...",
    totalPages,
  ];
};

const Paginationnumber = ({ totalPages }: { totalPages: number }) => {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const currentPage = Number(searchParams.get("page")) || 1;
 
  const createPageURL = (pageNumber: string | number) => {
    const params = new URLSearchParams(searchParams);
    params.set("page", pageNumber.toString());
    return `${pathname}?${params.toString()}`;
  };
 
  const allPages = generatePagination(currentPage, totalPages);
 
  const PaginationNumber = ({
    page,
    href,
    position,
    isActive,
  }: {
    page: number | string;
    href: string;
    position?: "first" | "last" | "middle" | "single";
    isActive: boolean;
  }) => {
    const className = clsx(
      "flex h-10 w-10 items-center justify-center text-sm border",
      {
        "rounded-l-sm": position === "first" || position === "single",
        "rounded-r-sm": position === "last" || position === "single",
        "z-10 bg-blue-700 border-blue-500 text-white bg-blue-700": isActive,
        "hover:bg-blue-700": !isActive && position !== "middle",
        "text-gray-300 pointer-events-none": position === "middle",
      }
    );
 
    return isActive && position === "middle" ? (
      <div className={className}>{page}</div>
    ) : (
      <Link href={href} className={className}>
        {page}
      </Link>
    );
  };
   
  const PaginationArrow = ({
    href,
    direction,
    isDisabled,
  }: {
    href: string;
    direction: "left" | "right";
    isDisabled?: boolean;
  }) => {
    const className = clsx(
      "flex h-10 w-10 items-center justify-center text-sm border",
      {
        "pointer-events-none text-blue-300": isDisabled,
        "hover:bg-blue-700": !isDisabled,
        "mr-2": direction === "left",
        "ml-2": direction === "right",
      }
    );
 
    const icon =
      direction === "left" ? (
        <HiChevronLeft size={20} />
      ) : (
        <HiChevronRight size={20} />
      );
 
    return isDisabled ? (
      <div className={className}>{icon}</div>
    ) : (
      <Link href={href} className={className}>
        {icon}
      </Link>
    );
  };
 
  return (
    <div className="inline-flex">
      <PaginationArrow
        direction="left"
        href={createPageURL(currentPage - 1)}
        isDisabled={currentPage <= 1}
      />
      <div className="flex -space-x-px">
        {allPages.map((page, index) => {
          let position: "first" | "last" | "single" | "middle" | undefined;
 
          if (index === 0) position = "first";
          if (index === allPages.length - 1) position = "last";
          if (allPages.length === 1) position = "single";
          if (page === "...") position = "middle";
 
          return (
            <PaginationNumber
              key={index}
              href={createPageURL(page)}
              page={page}
              position={position}
              isActive={currentPage === page} 
            />
          );
        })}
      </div>
      <PaginationArrow
        direction="right"
        href={createPageURL(currentPage + 1)}
        isDisabled={currentPage >= totalPages}
      />
    </div>
  );
};
 
export default Paginationnumber;
app\api\customer.tsx
//app\api\customer.tsx
export async function getCustomerList(page) {
  const response = await fetch(`http://127.0.0.1:5000/api/customerpagination/${page}/3`);
  const data = await response.json();
  //console.log(data);
  return {
    customerlist: data.customers,
    totalpage: data.total, 
    perpage: data.per_page
  }
}

export async function fetchCustomer(id) {
  const response = await fetch("http://127.0.0.1:5000/api/customer/"+id);
  return response.json()
}

export async function createCustomer(newCustomer) {
  const response = await fetch("http://127.0.0.1:5000/api/customer", {
    method: "POST",
    headers: { 
      "Content-Type": "application/json"
    },
    body: JSON.stringify(newCustomer)
  });
  console.log(response);
  return response.json()
}

export async function updateCustomer(updatedCustomer) {
  const response = await fetch(`http://127.0.0.1:5000/api/customer/${updatedCustomer.id}`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(updatedCustomer)
  });
  console.log(response)
  return response.json()
}

export async function deleteCustomer(id) {
  const response = await fetch(`http://127.0.0.1:5000/api/customer/${id}`, {
    method: "DELETE",
  });
  return response.json()
}
run C:\nextjs>npm run dev
python-guide-es.readthedocs.io/es/guide-es/dev/virtualenvs.html

Create an environment
ednalan@Cairocoders myapp % pip install virtualenv
ednalan@Cairocoders myapp % pip install virtualenv

Activate the environment
ednalan@Cairocoders myapp % source venv/bin/activate
(venv) ednalan@Cairocoders myapp %

Install Flask
pypi.org/project/Flask/
(venv) ednalan@Cairocoders myapp % pip install -U Flask
(venv) ednalan@Cairocoders myapp % flask run

Install requirements
pip install -U flask-cors
pypi.org/project/Flask-Cors/

Flask-SQLAlchemy
Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application.
flask-sqlalchemy.palletsprojects.com/en/3.0.x/

(venv) PS C:\flask_dev\myapp>pip install -U Flask-SQLAlchemy

Flask + marshmallow for beautiful APIs
pypi.org/project/flask-marshmallow/

(venv) PS C:\flask_dev\myapp>pip install flask-marshmallow

python3 -m pip install
https://pypi.org/project/pymysql/
app.py
 
//app.py
from flask import Flask, request, jsonify, make_response
from flask_cors import CORS, cross_origin #ModuleNotFoundError: No module named 'flask_cors' = pip install Flask-Cors
from models import db, Customer

app = Flask(__name__)

app.config['SECRET_KEY'] = 'cairocoders-ednalan'
#app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///flaskdb.db'
# Databse configuration mysql                             Username:password@hostname/databasename
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:root@localhost:8889/nextjsdb'
  
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = True
  
CORS(app, supports_credentials=True)
  
db.init_app(app)
     
with app.app_context():
    db.create_all()

# create a test route
@app.route('/test', methods=['GET'])
def test():
    return jsonify({'message': 'The server is running'})

# create a customer
@app.route('/api/customer', methods=['POST'])
def create_customer():
    try:
        data = request.get_json()
        new_customer = Customer(name=data['name'], email=data['email'], contactnumber=data['contactnumber'])
        db.session.add(new_customer)
        db.session.commit()  

        return jsonify({
            'id': new_customer.id,
            'name': new_customer.name,
            'email': new_customer.email,
            'contactnumber': new_customer.contactnumber
        }), 201  

    except Exception as e:
        return make_response(jsonify({'message': 'error creating customer', 'error': str(e)}), 500)
  
# get all customer
@app.route('/api/customer', methods=['GET'])
def get_customer():
    try:
        customer = Customer.query.all()
        customer_data = [{'id': customer.id, 'name': customer.name, 'email': customer.email} for customer in users]
        return jsonify(customer_data), 200
    except Exception as e:
        return make_response(jsonify({'message': 'error getting customer', 'error': str(e)}), 500)

@app.route('/api/customerpagination/<int:page>/<int:per_page>', methods=['GET'])
def customerpagination(page=1, per_page=3):
 
    total = Customer.query.count()
    print(total)
 
    customers = Customer.query.order_by(Customer.id.asc())  
    customers = customers.paginate(page=page, per_page=per_page)
 
    return jsonify({
        'total': total,
        'page': page,
        'per_page': per_page,
        'has_next': customers.has_next,
        'has_prev': customers.has_prev,
        'page_list': [iter_page if iter_page else '...' for iter_page in customers.iter_pages()],
        'customers': [{
            'id': p.id,
            'name': p.name,
            'email': p.email,
            'contactnumber': p.contactnumber,
        } for p in customers.items]
    })

# get a customers by id
@app.route('/api/customer/<id>', methods=['GET'])
def get_customerid(id):
    try:
        customer = Customer.query.filter_by(id=id).first() # get the first customer with the id
        if customer:
            return make_response(jsonify({'customer': customer.json()}), 200)
        return make_response(jsonify({'message': 'customer not found'}), 404) 
    except Exception as e:
        return make_response(jsonify({'message': 'error getting customer', 'error': str(e)}), 500)
  
# update a customer by id
@app.route('/api/customer/<id>', methods=['PUT'])
def update_customer(id):
    try:
        customer = Customer.query.filter_by(id=id).first()
        if customer:
            data = request.get_json()
            customer.name = data['name']
            customer.email = data['email']
            customer.contactnumber = data['contactnumber']
            db.session.commit()
            return make_response(jsonify({'message': 'customer updated'}), 200)
        return make_response(jsonify({'message': 'customer not found'}), 404)  
    except Exception as e:
        return make_response(jsonify({'message': 'error updating customer', 'error': str(e)}), 500)

# delete a customer by id
@app.route('/api/customer/<id>', methods=['DELETE'])
def delete_customer(id):
    try:
        customer = Customer.query.filter_by(id=id).first()
        if customer:
            db.session.delete(customer)
            db.session.commit()
            return make_response(jsonify({'message': 'customer deleted'}), 200)
        return make_response(jsonify({'message': 'customer not found'}), 404) 
    except Exception as e:
        return make_response(jsonify({'message': 'error deleting customer', 'error': str(e)}), 500)   
    
if __name__ == "__main__":
    app.run(debug=True)
models.py
 
//models.py
from flask_sqlalchemy import SQLAlchemy
    
db = SQLAlchemy()
    
class Customer(db.Model):
    __tablename__ = 'customer'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    contactnumber = db.Column(db.String(80), unique=True, nullable=False)

    def json(self):
        return {'id': self.id,'name': self.name, 'email': self.email, 'contactnumber': self.contactnumber}
run (venv) C:\flask_dev\myapp>flask run

Thursday, April 18, 2024

Next.js 14 Python Flask Dynamic Select Country State City Mysql | TailwindCSS DaisyUI

Next.js 14 Python Flask Dynamic Select Country State City Mysql | TailwindCSS DaisyUI

https://python-guide-es.readthedocs.io/es/guide-es/dev/virtualenvs.html

Create an environment
ednalan@Cairocoders myapp % pip install virtualenv
ednalan@Cairocoders myapp % pip install virtualenv

Activate the environment
ednalan@Cairocoders myapp % source venv/bin/activate
(venv) ednalan@Cairocoders myapp %

Install Flask
https://pypi.org/project/Flask/
(venv) ednalan@Cairocoders myapp % pip install -U Flask
(venv) ednalan@Cairocoders myapp % flask run

Install requirements
pip install -U flask-cors
https://pypi.org/project/Flask-Cors/

Flask-SQLAlchemy
Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application.
https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/

(venv) PS C:\flask_dev\myapp>pip install -U Flask-SQLAlchemy

python3 -m pip install
https://pypi.org/project/pymysql/
app.py
//app.py
from flask import Flask, jsonify
from flask_cors import CORS #ModuleNotFoundError: No module named 'flask_cors' = pip install Flask-Cors
from flask_marshmallow import Marshmallow #ModuleNotFoundError: No module named 'flask_marshmallow' = pip install flask-marshmallow https://pypi.org/project/flask-marshmallow/
   
from models import db, Countries, State, City
   
app = Flask(__name__)
CORS(app, supports_credentials=True)
   
#app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///flaskdb.db'
# Databse configuration mysql                             Username:password@hostname/databasename
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:root@localhost:8889/nextjsdb'
   
db.init_app(app)
     
with app.app_context():
    db.create_all()
   
ma=Marshmallow(app)
    
class CountriesSchema(ma.Schema):
    class Meta:
        fields = ('ID','countryName')
 
class StateSchema(ma.Schema):
    class Meta:
        fields = ('id','name','country_id')
 
class CitySchema(ma.Schema):
    class Meta:
        fields = ('id','name','stateid')
 
countries_schema = CountriesSchema(many=True)
state_schema = StateSchema(many=True)
city_schema = CitySchema(many=True)
 
@app.route("/")
def hello_world():
    return "Hello, World!"
   
@app.route('/listall',methods =['GET'])
def listall():
    all_countries = Countries.query.all()
    results = countries_schema.dump(all_countries)
    return jsonify(results)
 
@app.route('/state/<get_state>',methods =['GET'])
def statebycountry(get_state):
    state = State.query.filter_by(country_id=get_state).all()
    results = state_schema.dump(state)
    return jsonify(results)
 
@app.route('/city/<get_city>',methods =['GET'])
def city(get_city):
    city_data = City.query.filter_by(stateid=get_city).all()
    results = city_schema.dump(city_data)
    return jsonify(results)

if __name__ == "__main__":
    app.run(debug=True)
models.py
//models.py
from flask_sqlalchemy import SQLAlchemy
      
db = SQLAlchemy()
      
class Countries(db.Model):
    __tablename__ = "countries"
    ID = db.Column(db.Integer, primary_key=True)
    countryName = db.Column(db.String(120), index=True, unique=True)
 
class State(db.Model):
    __tablename__ = "state"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(120), index=True, unique=True)
    country_id = db.Column(db.Integer)
 
class City(db.Model):
    __tablename__ = "city"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(120), index=True, unique=True)
    stateid = db.Column(db.Integer)
Country State and City : https://github.com/cairocodes/cairocoders/blob/main/country-state-city.sql

run (venv) C:\flask_dev\myapp>flask run

Next.js
Install requirements
npm install axios
https://www.npmjs.com/package/axios

app\page.tsx
//app\page.tsx
import Form from "@/components/form";

export default function Home() {
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
      <div className="flex items-center justify-between gap-1 mb-5">
        <h1 className="text-4xl font-bold">Next.js 14 Python Flask Dynamic Select Country State City Mysql | TailwindCSS DaisyUI</h1>
      </div>    
      <div className="overflow-x-auto">
          <Form/>
      </div>  
    </div>
  );
}
components\form.tsx
//components\form.tsx
"use client";
 
import React, { useEffect, useState } from "react";
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios

export default function Form() {

    const [country, setCountry]= useState([]);
    const [countryid, setCountryid]= useState('0');
    const [stateid, setStateid]= useState('0');
    const [state, setState]= useState([]);
    const [city, setCity]= useState([]);

    useEffect(() => {
        const fetchCountry = async () => {
            axios.get('http://127.0.0.1:5000/listall')
            .then(function (response) {
                //handle success
                console.log(response)
                setCountry(response.data)
            })
            .catch(function (response) {
                //handle error
                console.log(response)
            });
        }
        fetchCountry()
    }, [])

    const handlecountry=(event)=>{
        const getcoutryid= event.target.value;
        console.log(getcoutryid);
        setCountryid(getcoutryid);
        event.preventDefault();
    }  
    
    const handlestate=(event)=>{
        const stateid= event.target.value;
        console.log(stateid);
        setStateid(stateid);
        event.preventDefault();
    }

    useEffect( ()=>{
        const getstate= async ()=>{  
            axios.get(`http://127.0.0.1:5000/state/${countryid }`)
            .then(function (response) {
                //handle success
                //console.log(response)
                setState(response.data)
            })
            .catch(function (response) {
                //handle error
                console.log(response)
            });
        }
        getstate();
    },[countryid]);

    useEffect( ()=>{
        const getcity= async ()=>{
            axios.get(`http://127.0.0.1:5000/city/${stateid }`)
            .then(function (response) {
                //handle success
                //console.log(response)
                setCity(response.data)
            })
            .catch(function (response) {
                //handle error
                console.log(response)
            });
        }
        getcity();
    },[stateid]);

    return (
        <>
        <div className="max-w-xl mx-auto mt-5">
            <div>
            <form>
            <div className="mb-5 pr-4 pl-4">
                <label className="block text-sm font-medium text-gray-900">
                    Country
                </label>
                <select onChange={(e)=>handlecountry(e)} name="country" className="select select-info w-full max-w-xs">
                    <option defaultValue={'DEFAULT'}>--Select Country--</option>
                    {
                     country.map( (getcon)=>(
                        <option key={getcon.ID} value={getcon.ID }> { getcon.countryName}</option>
                     ))
                    }
                </select>
            </div>
            <div className="mb-5 pr-4 pl-4">
                <label className="block text-sm font-medium text-gray-900">
                    State
                </label>
                <select onChange={(e)=>handlestate(e)} name="state" className="select select-success w-full max-w-xs">
                   <option defaultValue={'DEFAULT'}>--Select State--</option>
                   {
                     state.map( (st,index)=>(                    
                        <option key={index} value={st.id}>{ st.name}</option>
                     ))
                     }
                 </select>
            </div>
            <div className="mb-5 pr-4 pl-4">
                <label className="block text-sm font-medium text-gray-900">
                    City
                </label>
                <select name="city" className="select select-warning w-full max-w-xs">
                   <option>--Select City--</option>                  
                    {
                     city.map( (st,index)=>(                    
                        <option key={index} value={st.id}>{ st.name}</option>
                     ))
                    }
                 </select>
            </div>
        </form>
        </div>
        </div>
        
        </>
  );
}
run C:\nextjs>npm run dev

Thursday, April 11, 2024

Next.js 14 Python Flask Mysql Pagination Next Prev | TailwindCSS DaisyUI

Next.js 14 Python Flask Mysql Pagination Next Prev | TailwindCSS DaisyUI

https://python-guide-es.readthedocs.io/es/guide-es/dev/virtualenvs.html

Create an environment
ednalan@Cairocoders myapp % pip install virtualenv
ednalan@Cairocoders myapp % pip install virtualenv

Activate the environment
ednalan@Cairocoders myapp % source venv/bin/activate
(venv) ednalan@Cairocoders myapp %

Install Flask
https://pypi.org/project/Flask/
(venv) ednalan@Cairocoders myapp % pip install -U Flask
(venv) ednalan@Cairocoders myapp % flask run

Install requirements
pip install -U flask-cors
https://pypi.org/project/Flask-Cors/

Flask-SQLAlchemy
Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application.
https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/

(venv) PS C:\flask_dev\myapp>pip install -U Flask-SQLAlchemy

python3 -m pip install
https://pypi.org/project/pymysql/
app.py
//app.py
from flask import Flask, request, jsonify
from flask_cors import CORS, cross_origin #ModuleNotFoundError: No module named 'flask_cors' = pip install Flask-Cors
   
from models import db, Products

app = Flask(__name__)
   
app.config['SECRET_KEY'] = 'cairocoders-ednalan'
#app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///flaskdb.db'
# Databse configuration mysql                             Username:password@hostname/databasename
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:root@localhost:8889/nextjsdb'
   
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = True
   
CORS(app, supports_credentials=True)
   
db.init_app(app)
      
with app.app_context():
    db.create_all()
   
@app.route("/")
def hello_world():
    return "Hello, World!"
   
@app.route('/products/<int:page>/<int:per_page>', methods=['GET']) #http://127.0.0.1:5000/products/1/4
def products(page=1, per_page=3):
    
    nextpage = page + 1
    prevpage = page - 1
         
    total = Products.query.count()
     
    productdata = Products.query.order_by(Products.id.asc())  
    products = productdata.paginate(page=page, per_page=per_page)
     
    #print(products.next_num)
    #print(products.prev_num)
     
    nextnumber = products.next_num
    if nextnumber == None:
        next_page_url = ""
    else:
        next_page_url = f"http://127.0.0.1:5000/products/{nextpage}/{per_page}"
        
    prev_number = products.prev_num
    if prev_number == None:
        prev_page = ""
    else:
        prev_page = f"http://127.0.0.1:5000/products/{prevpage}/{per_page}"
         
    return jsonify({
        'total': total,
        'next_page_url': next_page_url,
        'prev_page_url': prev_page,
        'results': [{
            'id': rs.id,
            'name': rs.name,
            'image': rs.image,
            'price': rs.price
        } for rs in products.items]
    })
   
if __name__ == "__main__":
    app.run(debug=True)
models.py
//models.py
from flask_sqlalchemy import SQLAlchemy
       
db = SQLAlchemy()
       
class Products(db.Model):
    __tablename__ = "products"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), index=True)
    image = db.Column(db.String(150), index=True)
    price = db.Column(db.Integer,default=0)
run (venv) C:\flask_dev\myapp>flask run

Next.js
Install requirements
npm install axios
https://www.npmjs.com/package/axios

app\page.tsx
//app\page.tsx
import Link from "next/link";
import TableData from "@/components/tabledata";
import { Suspense } from "react";
import { Spinner } from "@/components/spinner";
 
export default function Home() {
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center bg-gray-300">
      <div className="flex items-center justify-between gap-1 mb-5">
        <h1 className="text-4xl font-bold">Next.js 14 Python Flask Mysql Pagination Next Prev | TailwindCSS DaisyUI</h1>
      </div>    
      <Suspense fallback={<Spinner />}>
        <TableData/>
      </Suspense>
    </div>
  );
}
components\tabledata.tsx
//components\tabledata.tsx
"use client";

import React, { useEffect, useState } from "react";
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios
import Image from 'next/image' 

export default function Users() {
    const [products, setProducts] = useState([]);
    const [info, setInfo] = useState({});

    const url = "http://127.0.0.1:5000/products/1/3";

    useEffect(() => {
        fetchProducts(url);
    }, []);

    const fetchProducts = (url) => {
        axios
        .get(url)
        .then((data) => {
            setProducts(data.data.results);
            //console.log(data.data.results);
            setInfo(data.data);
            //console.log(data.data);
        })
        .catch((error) => {
            console.log(error);
        });
    };

    const handleNextPage = () => {
        fetchProducts(info.next_page_url);
        window.scrollTo(0, 0);
    };
    
    const handlePreviousPage = () => {
        fetchProducts(info.prev_page_url);
        window.scrollTo(0, 0);
    };

    return (
            <div className="py-16">
            <div className="container mx-auto px-4">
                <h2 className="text-3xl font-bold text-white mb-8">Introducing Our Latest Product</h2>
                <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
                
                {products.map((item, index) => (
                <div key={index} className="bg-white rounded-lg shadow-lg p-8">
                    <div className="relative overflow-hidden">
                        <Image
                        src={`http://127.0.0.1:5000/static/images/${item.image}`}
                        width={400}
                        height={400}
                        alt="Photo"
                        />
                    <div className="absolute inset-0 bg-black opacity-40" />
                    <div className="absolute inset-0 flex items-center justify-center">
                        <button className="bg-white text-gray-900 py-2 px-6 rounded-full font-bold hover:bg-gray-300">View Product</button>
                    </div>
                    </div>
                    <h3 className="text-xl font-bold text-gray-900 mt-4">{item.name}</h3>
                    <p className="text-gray-500 text-sm mt-2">Description: {item.name}</p>
                    <div className="flex items-center justify-between mt-4">
                    <span className="text-gray-900 font-bold text-lg">${item.price}.99</span>
                    <button className="bg-gray-900 text-white py-2 px-4 rounded-full font-bold hover:bg-gray-800">Add to Cart</button>
                    </div>
                </div>
                ))}

                </div>
                <div className="w-1/2 items-center px-4 mt-6">   
                    <div className="join grid grid-cols-2">
                        {info.prev_page_url ? (
                            <button className="join-item btn btn-primary btn-outline" onClick={handlePreviousPage}>
                            Previous
                            </button>
                        ) : null}
                        {info.next_page_url ? (
                            <button className="join-item btn btn-primary btn-outline" onClick={handleNextPage}>
                            Next
                            </button>
                        ) : null}
                    </div>
                </div> 
            </div>
            </div>
  );
}
components\spinner.tsx
//components\spinner.tsx
export const Spinner = () => {
  return (
    <span className="loading loading-spinner loading-lg"></span>
  );
};
run C:\nextjs>npm run dev
https://github.com/cairocodes/Next.js-14-Python-Flask-Mysql-Pagination-Next-Prev-TailwindCSS-DaisyUI

Thursday, April 4, 2024

Next.js 14 Python Flask CRUD Mysql (Create Read Update and Delete)| TailwindCSS DaisyUI

Next.js 14 Python Flask CRUD Mysql (Create Read Update and Delete)| TailwindCSS DaisyUI

https://python-guide-es.readthedocs.io/es/guide-es/dev/virtualenvs.html

Create an environment
ednalan@Cairocoders myapp % pip install virtualenv
ednalan@Cairocoders myapp % pip install virtualenv

Activate the environment
ednalan@Cairocoders myapp % source venv/bin/activate
(venv) ednalan@Cairocoders myapp %

Install Flask
https://pypi.org/project/Flask/
(venv) ednalan@Cairocoders myapp % pip install -U Flask
(venv) ednalan@Cairocoders myapp % flask run

Install requirements
pip install -U flask-cors
https://pypi.org/project/Flask-Cors/

Flask-SQLAlchemy
Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application.
https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/

(venv) PS C:\flask_dev\myapp>pip install -U Flask-SQLAlchemy

Flask + marshmallow for beautiful APIs
https://pypi.org/project/flask-marshmallow/

(venv) PS C:\flask_dev\myapp>pip install flask-marshmallow

python3 -m pip install
https://pypi.org/project/pymysql/
app.py
 
//app.py
from flask import Flask, jsonify, request
from flask_marshmallow import Marshmallow #ModuleNotFoundError: No module named 'flask_marshmallow' = pip install flask-marshmallow https://pypi.org/project/flask-
from flask_cors import CORS, cross_origin #ModuleNotFoundError: No module named 'flask_cors' = pip install Flask-Cors
from models import db, Users
 
app = Flask(__name__)
 
app.config['SECRET_KEY'] = 'cairocoders-ednalan'
#app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///flaskdb.db'
# Databse configuration mysql                             Username:password@hostname/databasename
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:root@localhost:8889/flasknextjs' #python3 -m pip install PyMySQL https://pypi.org/project/pymysql/
                                                        
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = True
  
CORS(app, supports_credentials=True)
 
db.init_app(app)
        
with app.app_context():
    db.create_all()
 
ma=Marshmallow(app)
 
class UserSchema(ma.Schema):
    class Meta:
        fields = ('id','name','email','password')
  
user_schema = UserSchema()
users_schema = UserSchema(many=True)
  
@app.route("/")
def hello_world():
    return "<p>Hello, World!=</p>"
 
@app.route('/users', methods=['GET']) 
def listusers():
    all_users = Users.query.all()
    results = users_schema.dump(all_users)
    return jsonify(results)
  
@app.route('/userdetails/<id>',methods =['GET'])
def userdetails(id):
    user = Users.query.get(id)
    return user_schema.jsonify(user)
  
@app.route('/userupdate/<id>',methods = ['PUT'])
def userupdate(id):
    user = Users.query.get(id)
  
    name = request.json['name']
    email = request.json['email']
  
    user.name = name
    user.email = email
  
    db.session.commit()
    return user_schema.jsonify(user)
 
@app.route('/userdelete/<id>',methods=['DELETE'])
def userdelete(id):
    user = Users.query.get(id)
    db.session.delete(user)
    db.session.commit()
    return user_schema.jsonify(user)
  
@app.route('/newuser',methods=['POST'])
def newuser():
    name = request.json['name']
    email = request.json['email']
    password = request.json['password']
  
    print(name)
    print(email)
    print(password)
 
    users = Users(name=name, email=email, password=password)
 
    db.session.add(users)
    db.session.commit()
    return user_schema.jsonify(users)
 
if __name__ == "__main__":
    app.run(debug=True)
models.py
 
//models.py
from flask_sqlalchemy import SQLAlchemy #pip install -U Flask-SQLAlchemy
         
db = SQLAlchemy()
         
class Users(db.Model):
    __tablename__ = "tblusers"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), index=True)
    email = db.Column(db.String(150), index=True, unique=True)
    password = db.Column(db.String(255), index=True)
run (venv) C:\flask_dev\myapp>flask run

Next.js Install requirements
npm install axios
https://www.npmjs.com/package/axios

app\page.tsx
//app\page.tsx
import Link from "next/link";
import TableData from "@/components/tabledata";
import { Suspense } from "react";
import { Spinner } from "@/components/spinner";
 
export default function Home() {
    return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
      <div className="flex items-center justify-between gap-1 mb-5">
        <h1 className="text-4xl font-bold">Next.js 14 Python Flask CRUD Mysql (Create Read Update and Delete)| TailwindCSS DaisyUI</h1>
      </div>    
        <div className="overflow-x-auto">
          <div className="mb-2 w-full text-right">
            <Link
              href="/user/create"
              className="btn btn-primary">
              Create
            </Link>
          </div>
          <Suspense fallback={<Spinner />}>
            <TableData/>
          </Suspense>
      </div>  
    </div>
  ); 
}
components\tabledata.tsx
//components\tabledata.tsx
"use client";
  
import React, { useEffect, useState } from "react";
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios
import Link from "next/link";

export default function Users() {
    const [userData, setUSerData] = useState([]);
    useEffect(() => {
        fetchData();
    }, [])
 
    const fetchData = async () => {
        try {
            const result = await axios("http://127.0.0.1:5000/users");
            console.log(result.data);
            setUSerData(result.data)
        } catch (err) {
            console.log("somthing Wrong");
        }
    }

    const handleDelete=async(id)=>{
        console.log(id);
        await axios.delete("http://127.0.0.1:5000/userdelete/"+id);
        const newUserData=userData.filter((item)=>{
            return(
                item.id !==id
            )
        })
        setUSerData(newUserData);
    }
  return (
        <table className="table table-zebra">
        <thead className="text-sm text-gray-700 uppercase bg-gray-50">
            <tr>
            <th className="py-3 px-6">#</th>
            <th className="py-3 px-6">Name</th>
            <th className="py-3 px-6">Email</th>
            <th className="py-3 px-6">Password</th>
            <th className="py-3 px-6 text-center">Actions</th>
            </tr>
        </thead>
        <tbody>
            {userData.map((rs, index) => (
            <tr key={rs.id} className="bg-white border-b">
                <td className="py-3 px-6">{index + 1}</td>
                <td className="py-3 px-6">{rs.name}</td>
                <td className="py-3 px-6">{rs.email}</td>
                <td className="py-3 px-6">{rs.password}</td>
                <td className="flex justify-center gap-1 py-3">
                    <Link
                    href={`/user/view/${rs.id}`} 
                    className="btn btn-info">
                    View
                    </Link>
                    <Link
                    href={`/user/edit/${rs.id}`} 
                    className="btn btn-primary">
                    Edit
                    </Link>
                    <button onClick={()=>handleDelete(rs.id)} className="btn btn-secondary">Delete</button>
                </td>
            </tr>
            ))}
        </tbody>
        </table>
  );
}
components\spinner.tsx
//components\spinner.tsx
export const Spinner = () => {
  return (
    <span className="loading loading-spinner loading-lg"></span>
  );
};
app\user\create\page.tsx
//app\user\create\page.tsx
"use client";

import React, { useState } from "react";
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios

const CreateUserPage = () => {
    const [userField, setUserField] = useState({
        name: "",
        email: "",
        password: ""
    });
 
    const changeUserFieldHandler = (e) => {
        setUserField({
            ...userField,
            [e.target.name]: e.target.value
        });
        //console.log(userField);
    }

    const onSubmitChange = async (e) => {
        e.preventDefault();
        try {
          const responce= await axios.post("http://127.0.0.1:5000/newuser", userField);
          console.log(responce)
          window.location.href = '/';
        } catch (err) {
            console.log("Something Wrong");
        }
    }
    return (
    <div className="max-w-md mx-auto mt-5">
        <h1 className="text-2xl text-center mb-2">Add New User</h1>
        <div>
        <form>
        <div className="mb-5">
          <label htmlFor="name" className="block text-sm font-medium text-gray-900">
            Full Name
          </label>
          <input
            type="text"
            name="name"
            id="name"
            className="input input-bordered input-primary w-full max-w-xs"
            placeholder="Full Name..."
            onChange={e => changeUserFieldHandler(e)} 
          />
        </div>
        <div className="mb-5">
          <label htmlFor="email" className="block text-sm font-medium text-gray-900">
            Email
          </label>
          <input
            type="email"
            name="email"
            id="email"
            className="input input-bordered input-primary w-full max-w-xs"
            placeholder="Email..."
            onChange={e => changeUserFieldHandler(e)}
          />
        </div>
        <div className="mb-5">
          <label
            htmlFor="password" className="block text-sm font-medium text-gray-900">
            Password
          </label>
          <input
            type="text"
            name="password"
            id="password"
            className="input input-bordered input-primary w-full max-w-xs"
            placeholder="Password..."
            onChange={e => changeUserFieldHandler(e)}
          />
        </div>
         <button type="submit" className="btn btn-primary" onClick={e => onSubmitChange(e)}>Add User</button> 
      </form>
    </div>
    </div>
  );
};
  
export default CreateUserPage;
app\user\edit\[id]\page.tsx
//app\user\edit\[id]\page.tsx
"use client";

import React, { useState, useEffect } from 'react';
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios
import Link from "next/link";
import { useParams } from 'next/navigation'

export default function ViewUserPage() {
    const {id}=useParams();

    console.log(id);
 
    const [userField, setUserField] = useState({
        name: "",
        email: ""
    });

    useEffect(()=>{
        fetchUser();
    },[id]);
 
    const fetchUser=async()=>{
        try{
            const result=await axios.get("http://127.0.0.1:5000/userdetails/"+id);
            console.log(result.data);
            setUserField(result.data)
 
        }catch(err){
            console.log("Something Wrong");
        }
    }

    const changeUserFieldHandler = (e) => {
        setUserField({
            ...userField,
            [e.target.name]: e.target.value
        });
        console.log(userField);
    }
     
    const onSubmitChange = async (e) => {
        e.preventDefault();
        try {
            await axios.put("http://127.0.0.1:5000/userupdate/"+id, userField);
            window.location.href = '/';
        } catch (err) {
            console.log("Something Wrong");
        }
    }

    return (
    <div className="max-w-md mx-auto mt-5">
      <h1 className="text-2xl text-center mb-2">Edit Form</h1>
            <form>
                <div className="mb-3 mt-3">
                    <label className="block text-sm font-medium text-gray-900"> ID:</label>
                    <input type="text" id="id" name="id" value={id} disabled />
                </div>
                <div className="mb-3 mt-3">
                    <label className="block text-sm font-medium text-gray-900"> Full Name:</label>
                    <input type="text" className="input input-bordered input-primary w-full max-w-xs" placeholder="Enter Your Full Name" name="name" value={userField.name} onChange={e => changeUserFieldHandler(e)} />
                </div>
                <div className="mb-3 mt-3">
                    <label className="block text-sm font-medium text-gray-900">Email:</label>
                    <input type="email" className="input input-bordered input-primary w-full max-w-xs" id="email" placeholder="Enter email" name="email" value={userField.email}  onChange={e => changeUserFieldHandler(e)}/>
                </div>
                <div className="mb-3 mt-3">
                     <label className="block text-sm font-medium text-gray-900">Password:</label>
                      <input type="text" className="input input-bordered input-primary w-full max-w-xs" id="password" placeholder="Enter password" name="password" onChange={e => changeUserFieldHandler(e)} required/>
                </div>
                <button type="submit" className="btn btn-primary" onClick={e=>onSubmitChange(e)}>Update</button>
            </form>
    </div>
  );
}
app\user\view\[id]\page.tsx
//app\user\view\[id]\page.tsx
"use client";

import React, { useState, useEffect } from 'react';
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios
import Link from "next/link";
import { useParams } from 'next/navigation'

export default function ViewUserPage() {
    const {id}=useParams();

    console.log(id);

    const[user,setUser]=useState([]);
 
    useEffect(()=>{
        fetchUser();
    },[id]);
 
    const fetchUser=async()=>{
        try{
        const result=await axios.get("http://127.0.0.1:5000/userdetails/"+id);
        console.log(result.data);
        setUser(result.data)
 
        }catch(err){
            console.log("Something Wrong");
        }
    }

    return (
    <div className="max-w-md mx-auto mt-5">
      <h1 className="text-2xl text-center mb-2">View User</h1>
      <table className="table table-zebra">
          <thead className="text-sm text-gray-700 uppercase bg-gray-50">
            <tr>
              <th>S No.</th>
              <th>Full Name</th>
              <th>Email</th>             
            </tr>
          </thead>
          <tbody>
            <tr>
                <td>{user.id}</td>
                <td>{user.name}</td>
                <td>{user.email}</td>
            </tr>
          </tbody>
      </table>
    </div>
  );
}
run C:\nextjs>npm run dev
Github - Next.js 14 Python Flask CRUD Mysql (Create Read Update and Delete)| TailwindCSS DaisyUI

Next.js 14 Python Flask Mysql List All Data | TailwindCSS DaisyUI

Next.js 14 Python Flask Mysql List All Data | TailwindCSS DaisyUI

https://python-guide-es.readthedocs.io/es/guide-es/dev/virtualenvs.html

Create an environment
ednalan@Cairocoders myapp % pip install virtualenv
ednalan@Cairocoders myapp % pip install virtualenv

Activate the environment
ednalan@Cairocoders myapp % source venv/bin/activate
(venv) ednalan@Cairocoders myapp %

Install Flask
https://pypi.org/project/Flask/
(venv) ednalan@Cairocoders myapp % pip install -U Flask
(venv) ednalan@Cairocoders myapp % flask run

Install requirements
pip install -U flask-cors
https://pypi.org/project/Flask-Cors/

Flask-SQLAlchemy
Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application.
https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/

(venv) PS C:\flask_dev\myapp>pip install -U Flask-SQLAlchemy

Flask + marshmallow for beautiful APIs
https://pypi.org/project/flask-marshmallow/

(venv) PS C:\flask_dev\myapp>pip install flask-marshmallow

python3 -m pip install
https://pypi.org/project/pymysql/
app.py
 
//app.py
from flask import Flask, jsonify, request
from flask_marshmallow import Marshmallow #ModuleNotFoundError: No module named 'flask_marshmallow' = pip install flask-marshmallow 
from flask_cors import CORS, cross_origin #ModuleNotFoundError: No module named 'flask_cors' = pip install Flask-Cors
from models import db, Users
 
app = Flask(__name__)
 
app.config['SECRET_KEY'] = 'cairocoders-ednalan'
#app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///flaskdb.db'
# Databse configuration mysql                             Username:password@hostname/databasename
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:root@localhost:8889/flasknextjs' #python3 -m pip install PyMySQL https://pypi.org/project/pymysql/
                                                        
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = True
  
CORS(app, supports_credentials=True)
 
db.init_app(app)
        
with app.app_context():
    db.create_all()
 
ma=Marshmallow(app)
 
class UserSchema(ma.Schema):
    class Meta:
        fields = ('id','name','email','password')
  
user_schema = UserSchema()
users_schema = UserSchema(many=True)

@app.route("/")
def hello():
    return "Hello, World!"
 
@app.route('/users', methods=['GET']) 
def listusers():
    all_users = Users.query.all()
    results = users_schema.dump(all_users)
    return jsonify(results)

if __name__ == "__main__":
    app.run(debug=True)
models.py
 
//models.py
from flask_sqlalchemy import SQLAlchemy #pip install -U Flask-SQLAlchemy
         
db = SQLAlchemy()
         
class Users(db.Model):
    __tablename__ = "tblusers"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), index=True, unique=True)
    email = db.Column(db.String(150), index=True, unique=True)
    password = db.Column(db.String(255), index=True, unique=True)
run (venv) C:\flask_dev\myapp>flask run

Next.js Install requirements
npm install axios
https://www.npmjs.com/package/axios

app\page.tsx
//app\page.tsx
"use client";
  
import React, { useEffect, useState } from "react";
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios
import Link from "next/link";

export default function Home() {
    const [userData, setUSerData] = useState([]);
    useEffect(() => {
        fetchData();
    }, [])
 
    const fetchData = async () => {
        try {
            const result = await axios("http://127.0.0.1:5000/users");
            console.log(result.data);
            setUSerData(result.data)
        } catch (err) {
            console.log("somthing Wrong");
        }
    }

  return (
<div className="w-screen py-20 flex justify-center flex-col items-center">
      <div className="flex items-center justify-between gap-1 mb-5">
        <h1 className="text-4xl font-bold">Next.js 14 Python Flask Mysql List All Data | TailwindCSS DaisyUI</h1>
      </div>    
      <div className="overflow-x-auto">
          <div className="mb-2 w-full text-right">
            <Link
              href="#"
              className="btn btn-primary">
              Create
            </Link>
          </div>
        <table className="table table-zebra">
        <thead className="text-sm text-gray-700 uppercase bg-gray-50">
            <tr>
            <th className="py-3 px-6">#</th>
            <th className="py-3 px-6">Name</th>
            <th className="py-3 px-6">Email</th>
            <th className="py-3 px-6">Password</th>
            <th className="py-3 px-6 text-center">Actions</th>
            </tr>
        </thead>
        <tbody>
            {userData.map((rs, index) => (
            <tr key={rs.id} className="bg-white border-b">
                <td className="py-3 px-6">{index + 1}</td>
                <td className="py-3 px-6">{rs.name}</td>
                <td className="py-3 px-6">{rs.email}</td>
                <td className="py-3 px-6">{rs.password}</td>
                <td className="flex justify-center gap-1 py-3">
                    <button className="btn btn-active btn-primary">Edit</button>
                    <button className="btn btn-active btn-secondary">Delete</button>
                </td>
            </tr>
            ))}
        </tbody>
        </table>
      </div>  
    </div>
  );
}
run C:\nextjs>npm run dev
Github - Next.js 14 Python Flask Mysql List All Data | TailwindCSS DaisyUI

Wednesday, April 3, 2024

Next.Js Python Flask

Next.Js Python Flask

https://python-guide-es.readthedocs.io/es/guide-es/dev/virtualenvs.html

Create an environment
ednalan@Cairocoders myapp % pip install virtualenv
ednalan@Cairocoders myapp % pip install virtualenv

Activate the environment
ednalan@Cairocoders myapp % source venv/bin/activate
(venv) ednalan@Cairocoders myapp %

Install Flask
https://pypi.org/project/Flask/
(venv) ednalan@Cairocoders myapp % pip install -U Flask
(venv) ednalan@Cairocoders myapp % flask run

Install requirements
pip install -U flask-cors
https://pypi.org/project/Flask-Cors/

app.py
//app.py
from flask import Flask, jsonify
from flask_cors import CORS #pip install -U flask-cors https://pypi.org/project/Flask-Cors/

# app instance
app = Flask(__name__)
CORS(app)

@app.route("/")
def hello():
    return "Hello, World!"

# /api/user
@app.route("/api/user", methods=['GET'])
def return_home():
    return jsonify({
        'name': ['cairocoders', 'clydey', 'caitlyn'],
        'email': ['cairocoders@gmail.com', 'clydey@gmail.com', 'caitlyn@gmail.com']
    })


if __name__ == "__main__":
    app.run(debug=True, port=8080)
Install Nextjs 14
https://nextjs.org/docs/getting-started/installation
app\page.tsx
//app\page.tsx
"use client";
  
import React, { useEffect, useState } from "react";

export default function Home() {
  const [message, setMessage] = useState("Loading");
  const [userData, setUSerData] = useState([]);
            
    fetch("http://127.0.0.1:5000/api/user")
      .then((response) => response.json())
      .then((data) => {
        console.log(data);
        setMessage(data.message);
        setUSerData(data.name)
      });
  }, []);

  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <h3>Next js Python Flask</h3>
      <div>{message}</div>
      {userData.map((user, index) => (
        <div key={index}>{user}</div>
      ))}
    </main>
  );
}
app\page.tsx axios
"use client";
  
import React, { useEffect, useState } from "react";
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios

export default function Home() {
    const [userData, setUSerData] = useState([]);
    useEffect(() => {
        fetchData();
    }, [])
 
    const fetchData = async () => {
        try {
            const result = await axios("http://127.0.0.1:5000/api/user");
            console.log(result.data);
            setUSerData(result.data.name)
        } catch (err) {
            console.log("somthing Wrong");
        }
    }

  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <h3>Next js Python Flask</h3>
      {userData.map((user, index) => (
        <div key={index}>{user}</div>
      ))}
    </main>
  );
}

Friday, June 9, 2023

Python Flask Login Register with CRUD Books and Admin LTE Integration

Python Flask Login Register with CRUD Books and Admin LTE Integration

Python Flask 

https://flask.palletsprojects.com/en/2.3.x/installation/
 
Create an environment
C:\flask_dev>py -3 -m venv venv

Activate the environment
C:\flask_dev>venv\Scripts\activate

Install Flask
venv C:\flask_dev>pip install Flask
C:\flask_dev\myapp\app.py

Install requirements

Flask-SQLAlchemy
Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application.
https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/

(venv) PS C:\flask_dev\myapp>pip install -U Flask-SQLAlchemy

Flask-Bcrypt
Flask-Bcrypt is a Flask extension that provides bcrypt hashing utilities for your application.
https://pypi.org/project/Flask-Bcrypt/

(venv) PS C:\flask_dev\myapp>pip install Flask-Bcrypt

C:\flask_dev\myapp\app.py
 
#C:\flask_dev\myapp\app.py
from flask import Flask, render_template, request, redirect, url_for, session
from flask_bcrypt import Bcrypt #pip install Flask-Bcrypt = https://pypi.org/project/Flask-Bcrypt/
from werkzeug.utils import secure_filename
import os

from models import db, Users, Books 
  
app = Flask(__name__)
   
app.config['SECRET_KEY'] = 'cairocoders-ednalan'
#app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///flaskdb.db'
# Databse configuration mysql                             Username:password@hostname/databasename
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:''@localhost/library-system'
 
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = True
  
bcrypt = Bcrypt(app) 
 
db.init_app(app)
        
with app.app_context():
    db.create_all()


app.config['UPLOAD_FOLDER'] = 'static/images'
   
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])
   
def allowed_file(filename):
 return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/register', methods =['GET', 'POST'])
def register():
    mesage = ''
    if request.method == 'POST' and 'name' in request.form and 'password' in request.form and 'email' in request.form :
        fullname = request.form['name']
        password = request.form['password']
        email = request.form['email']
        
        user_exists = Users.query.filter_by(email=email).first() is not None
      
        if user_exists:
            mesage = 'Email already exists !'
        elif not re.match(r'[^@]+@[^@]+\.[^@]+', email):
            mesage = 'Invalid email address !'
        elif not fullname or not password or not email:
            mesage = 'Please fill out the form !'
        else:
            hashed_password = bcrypt.generate_password_hash(password)
            new_user = Users(name=fullname, email=email, password=hashed_password)
            db.session.add(new_user)
            db.session.commit()
            mesage = 'You have successfully registered !'
    elif request.method == 'POST':
        mesage = 'Please fill out the form !'
    return render_template('register.html', mesage = mesage)
    
@app.route('/login', methods =['GET', 'POST'])
def login():
    mesage = ''
    if request.method == 'POST':
        email = request.form['email']
        password = request.form['password']
        #print(email)
        #print(password)
        if email == '' or password == '':
            mesage = 'Please enter email and password !'
        else:
            user = Users.query.filter_by(email=email).first()
            print(user)

            if user is None:
                mesage = 'Please enter correct email / password !' 
            else:
                if not bcrypt.check_password_hash(user.password, password):
                    mesage = 'Please enter correct email and password !'
                else:    
                    session['loggedin'] = True
                    session['userid'] = user.id
                    session['name'] = user.name
                    session['email'] = user.email
                    mesage = 'Logged in successfully !'            
                    return redirect(url_for('dashboard'))

    return render_template('login.html', mesage = mesage)

@app.route("/dashboard", methods =['GET', 'POST'])
def dashboard():
    if 'loggedin' in session:        
        return render_template("dashboard.html")
    return redirect(url_for('login'))   

@app.route('/logout')
def logout():
    session.pop('loggedin', None)
    session.pop('userid', None)
    session.pop('email', None)
    return redirect(url_for('login'))

# Manage Books   
@app.route("/books", methods =['GET', 'POST'])
def books():
    if 'loggedin' in session:
        books = Books.query.all()

        return render_template("books.html", books = books)
    return redirect(url_for('login'))

@app.route('/save_book',methods=['POST'])
def save_book():
    msg = ''    
    if 'loggedin' in session:
        if request.method == 'POST':
            name = request.form['name'] 
            isbn = request.form['isbn']  
            action = request.form['action']

            if action == 'updateBook':
                bookid = request.form['bookid']
                book = Books.query.get(bookid)
            
                book.name = name
                book.isbn = isbn

                db.session.commit()
                print("UPDATE book") 
            else:
                file = request.files['uploadFile']
                filename = secure_filename(file.filename)
                print(filename)
                if file and allowed_file(file.filename):
                    file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
                    filenameimage = file.filename

                    book = Books(name=name, picture=filenameimage, isbn=isbn)
                    db.session.add(book)
                    db.session.commit()
                    print("INSERT INTO book")  
                else:
                    msg  = 'Invalid Uplaod only png, jpg, jpeg, gif' 
            return redirect(url_for('books'))        
        elif request.method == 'POST':
            msg = 'Please fill out the form !'        
        return render_template("books.html", msg = msg)
    return redirect(url_for('login'))

@app.route("/edit_book", methods =['GET', 'POST'])
def edit_book():
    msg = ''    
    if 'loggedin' in session:
        bookid = request.args.get('bookid')
        print(bookid)
        books = Books.query.get(bookid)
        
        return render_template("edit_books.html", books = books)
    return redirect(url_for('login'))

@app.route("/delete_book", methods =['GET'])
def delete_book():
    if 'loggedin' in session:
        bookid = request.args.get('bookid')
        book = Books.query.get(bookid)
        print(book.picture)
        db.session.delete(book)
        db.session.commit()
        os.unlink(os.path.join(app.config['UPLOAD_FOLDER'], book.picture))
        return redirect(url_for('books'))
    return redirect(url_for('login'))   

if __name__=='__main__':
    app.run(debug=True)
C:\flask_dev\myapp\models.py
 
#C:\flask_dev\myapp\models.py
from flask_sqlalchemy import SQLAlchemy
         
db = SQLAlchemy()
         
class Users(db.Model):
    __tablename__ = "user"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), index=True, unique=True)
    email = db.Column(db.String(150), index=True, unique=True)
    password = db.Column(db.String(255), index=True, unique=True)

class Books(db.Model):
    __tablename__ = "tblbook"
    bookid = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), index=True, unique=True)
    picture = db.Column(db.String(150), index=True, unique=True)
    isbn = db.Column(db.String(255), index=True, unique=True)
Download Bootstrap AdminLTE
https://github.com/ColorlibHQ/AdminLTE/releases
templates/register.html
//
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>AdminLTE 3 | Registration Page</title>

  <!-- Google Font: Source Sans Pro -->
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
  <!-- Font Awesome -->
  <link rel="stylesheet" href="{{ url_for('static',filename='plugins/fontawesome-free/css/all.min.css') }}">
  <!-- icheck bootstrap -->
  <link rel="stylesheet" href="{{ url_for('static',filename='plugins/icheck-bootstrap/icheck-bootstrap.min.css') }}">
  <!-- Theme style -->
  <link rel="stylesheet" href="{{ url_for('static',filename='css/adminlte.min.css') }}">
</head>
<body class="hold-transition register-page">
<div class="register-box">
  <div class="register-logo">
    <a href="../../index2.html"><b>Admin</b>LTE</a>
  </div>

  <div class="card">
    <div class="card-body register-card-body">
      <p class="login-box-msg">Register a new membership</p>

		<form action="{{ url_for('register') }}" method="post">
        {% if mesage is defined and mesage %}
			<div class="alert alert-warning">{{ mesage }}</div>
		{% endif %}
		
        <div class="input-group mb-3">
          <input type="text" class="form-control" id="name" name="name" placeholder="Full name">
          <div class="input-group-append">
            <div class="input-group-text">
              <span class="fas fa-user"></span>
            </div>
          </div>
        </div>
        <div class="input-group mb-3">
          <input type="email" class="form-control" id="email" name="email" placeholder="Email">
          <div class="input-group-append">
            <div class="input-group-text">
              <span class="fas fa-envelope"></span>
            </div>
          </div>
        </div>
        <div class="input-group mb-3">
          <input type="password" class="form-control" id="password" name="password" placeholder="Password">
          <div class="input-group-append">
            <div class="input-group-text">
              <span class="fas fa-lock"></span>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="col-8">
            <div class="icheck-primary">
              <input type="checkbox" id="agreeTerms" name="terms" value="agree">
              <label for="agreeTerms">
               I agree to the <a href="#">terms</a>
              </label>
            </div>
          </div>
          <!-- /.col -->
          <div class="col-4">
            <button type="submit" class="btn btn-primary btn-block">Register</button>
          </div>
          <!-- /.col -->
        </div>
      </form>

      <div class="social-auth-links text-center">
        <p>- OR -</p>
        <a href="#" class="btn btn-block btn-primary">
          <i class="fab fa-facebook mr-2"></i>
          Sign up using Facebook
        </a>
        <a href="#" class="btn btn-block btn-danger">
          <i class="fab fa-google-plus mr-2"></i>
          Sign up using Google+
        </a>
      </div>

      <a href="{{url_for('login')}}" class="text-center">I already have an account</a> 
    </div>
    <!-- /.form-box -->
  </div><!-- /.card -->
</div>
<!-- /.register-box -->

<!-- jQuery -->
<script src="{{ url_for('static',filename='plugins/jquery/jquery.min.js') }}"></script>
<!-- Bootstrap 4 -->
<script src="{{ url_for('static',filename='plugins/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
<!-- AdminLTE App -->
<script src="{{ url_for('static',filename='js/adminlte.min.js') }}"></script>
</body>
</html>
templates/login.html
//templates/login.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>AdminLTE 3 | Log in</title>

  <!-- Google Font: Source Sans Pro -->
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
  <!-- Font Awesome -->
  <link rel="stylesheet" href="{{ url_for('static',filename='plugins/fontawesome-free/css/all.min.css') }}">
  <!-- icheck bootstrap -->
  <link rel="stylesheet" href="{{ url_for('static',filename='plugins/icheck-bootstrap/icheck-bootstrap.min.css') }}">
  <!-- Theme style -->
  <link rel="stylesheet" href="{{ url_for('static',filename='css/adminlte.min.css') }}">
</head>
<body class="hold-transition login-page">
<div class="login-box">
  <div class="login-logo">
    <a href="../../index2.html"><b>Admin</b>LTE</a>
  </div>
  <!-- /.login-logo -->
  <div class="card">
    <div class="card-body login-card-body">
      <p class="login-box-msg">Sign in to start your session</p>

      <form action="{{ url_for('login') }}" method="post">
        {% if mesage is defined and mesage %}
          <div class="alert alert-warning">{{ mesage }}</div>
        {% endif %}

        <div class="input-group mb-3">
          <input type="email" class="form-control" id="email" name="email" placeholder="Email">
          <div class="input-group-append">
            <div class="input-group-text">
              <span class="fas fa-envelope"></span>
            </div>
          </div>
        </div>
        <div class="input-group mb-3">
          <input type="password" class="form-control" id="password" name="password" placeholder="Password">
          <div class="input-group-append">
            <div class="input-group-text">
              <span class="fas fa-lock"></span>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="col-8">
            <div class="icheck-primary">
              <input type="checkbox" id="remember">
              <label for="remember">
                Remember Me
              </label>
            </div>
          </div>
          <!-- /.col -->
          <div class="col-4">
            <button type="submit" class="btn btn-primary btn-block">Sign In</button>
          </div>
          <!-- /.col -->
        </div>
      </form>

      <div class="social-auth-links text-center mb-3">
        <p>- OR -</p>
        <a href="#" class="btn btn-block btn-primary">
          <i class="fab fa-facebook mr-2"></i> Sign in using Facebook
        </a>
        <a href="#" class="btn btn-block btn-danger">
          <i class="fab fa-google-plus mr-2"></i> Sign in using Google+
        </a>
      </div>
      <!-- /.social-auth-links -->

      <p class="mb-1">
        <a href="forgot-password.html">I forgot my password</a>
      </p>
      <p class="mb-0">
        <a href="{{url_for('register')}}" class="text-center">Register a new account</a>
      </p>
    </div>
    <!-- /.login-card-body -->
  </div>
</div>
<!-- /.login-box -->

<!-- jQuery -->
<script src="{{ url_for('static',filename='plugins/jquery/jquery.min.js') }}"></script>
<!-- Bootstrap 4 -->
<script src="{{ url_for('static',filename='plugins/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
<!-- AdminLTE App -->
<script src="{{ url_for('static',filename='js/adminlte.min.js') }}"></script>
</body>
</html>
templates/dashboard.html
//templates/dashboard.html
{% extends 'layout.html' %}
 
{% block body %}
    <!-- Content Header (Page header) -->
    <div class="content-header">
      <div class="container-fluid">
        <div class="row mb-2">
          <div class="col-sm-6">
            <h1 class="m-0">Dashboard</h1>
          </div><!-- /.col -->
          <div class="col-sm-6">
            <ol class="breadcrumb float-sm-right">
              <li class="breadcrumb-item"><a href="#">Home</a></li>
              <li class="breadcrumb-item active">Dashboard v1</li>
            </ol>
          </div><!-- /.col -->
        </div><!-- /.row -->
      </div><!-- /.container-fluid -->
    </div>
    <!-- /.content-header -->

    <!-- Main content -->
    <section class="content">
      <div class="container-fluid">
        <!-- Small boxes (Stat box) -->
        <div class="row">
          <div class="col-lg-3 col-6">
            <!-- small box -->
            <div class="small-box bg-info">
              <div class="inner">
                <h3>150</h3>

                <p>Total Books</p>
              </div>
              <div class="icon">
                <i class="ion ion-bag"></i>
              </div>
              <a href="#" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
            </div>
          </div>
          <!-- ./col -->
          <div class="col-lg-3 col-6">
            <!-- small box -->
            <div class="small-box bg-success">
              <div class="inner">
                <h3>53<sup style="font-size: 20px">%</sup></h3>

                <p>Available Books</p>
              </div>
              <div class="icon">
                <i class="ion ion-stats-bars"></i>
              </div>
              <a href="#" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
            </div>
          </div>
          <!-- ./col -->
          <div class="col-lg-3 col-6">
            <!-- small box -->
            <div class="small-box bg-warning">
              <div class="inner">
                <h3>44</h3>

                <p>Returned Books</p>
              </div>
              <div class="icon">
                <i class="ion ion-person-add"></i>
              </div>
              <a href="#" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
            </div>
          </div>
          <!-- ./col -->
          <div class="col-lg-3 col-6">
            <!-- small box -->
            <div class="small-box bg-danger">
              <div class="inner">
                <h3>65</h3>

                <p>Issued Books</p>
              </div>
              <div class="icon">
                <i class="ion ion-pie-graph"></i>
              </div>
              <a href="#" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
            </div>
          </div>
          <!-- ./col -->
        </div>
        <!-- /.row -->
        
        <!-- /.row (main row) -->
      </div><!-- /.container-fluid -->
    </section>
    <!-- /.content -->
{% endblock %}
templates/layout.html
//templates/layout.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>AdminLTE 3 | Dashboard</title>

  <!-- Google Font: Source Sans Pro -->
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
  <!-- Font Awesome -->
  <link rel="stylesheet" href="{{ url_for('static',filename='plugins/fontawesome-free/css/all.min.css') }}">
  <!-- Ionicons -->
  <link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
  <!-- iCheck -->
  <link rel="stylesheet" href="{{ url_for('static',filename='plugins/icheck-bootstrap/icheck-bootstrap.min.css') }}">
  <!-- Theme style -->
  <link rel="stylesheet" href="{{ url_for('static',filename='css/adminlte.min.css') }}">
</head>
<body class="hold-transition sidebar-mini layout-fixed">
<div class="wrapper">

  <!-- Preloader -->
  <div class="preloader flex-column justify-content-center align-items-center">
    <img class="animation__shake" src="{{ url_for('static',filename='img/AdminLTELogo.png') }}" alt="AdminLTELogo" height="60" width="60">
  </div>

  <!-- Navbar -->
  <nav class="main-header navbar navbar-expand navbar-white navbar-light">
    <!-- Left navbar links -->
    <ul class="navbar-nav">
      <li class="nav-item">
        <a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a>
      </li>
      <li class="nav-item d-none d-sm-inline-block">
        <a href="index3.html" class="nav-link">Home</a>
      </li>
      <li class="nav-item d-none d-sm-inline-block">
        <a href="#" class="nav-link">Contact</a>
      </li>
    </ul>

    <!-- Right navbar links -->
    <ul class="navbar-nav ml-auto">
      <!-- Navbar Search -->
      <li class="nav-item">
        <a class="nav-link" data-widget="navbar-search" href="#" role="button">
          <i class="fas fa-search"></i>
        </a>
        <div class="navbar-search-block">
          <form class="form-inline">
            <div class="input-group input-group-sm">
              <input class="form-control form-control-navbar" type="search" placeholder="Search" aria-label="Search">
              <div class="input-group-append">
                <button class="btn btn-navbar" type="submit">
                  <i class="fas fa-search"></i>
                </button>
                <button class="btn btn-navbar" type="button" data-widget="navbar-search">
                  <i class="fas fa-times"></i>
                </button>
              </div>
            </div>
          </form>
        </div>
      </li>
      <li class="nav-item">
        <a class="nav-link" data-widget="control-sidebar" data-controlsidebar-slide="true" href="#" role="button">
          <i class="fas fa-th-large"></i>
        </a>
      </li>
    </ul>
  </nav>
  <!-- /.navbar -->

  <!-- Main Sidebar Container -->
  <aside class="main-sidebar sidebar-dark-primary elevation-4">
    <!-- Brand Logo -->
    <a href="index3.html" class="brand-link">
      <img src="{{ url_for('static',filename='img/AdminLTELogo.png') }}" alt="AdminLTE Logo" class="brand-image img-circle elevation-3" style="opacity: .8">
      <span class="brand-text font-weight-light">AdminLTE 3</span>
    </a>

    <!-- Sidebar -->
    <div class="sidebar">
      <!-- Sidebar user panel (optional) -->
      <div class="user-panel mt-3 pb-3 mb-3 d-flex">
        <div class="image">
          <img src="{{ url_for('static',filename='img/avatar4.png') }}" class="img-circle elevation-2" alt="User Image">
        </div>
        <div class="info">
          <a href="#" class="d-block">{{session.name}}</a>
        </div>
      </div>

      <!-- SidebarSearch Form -->
      <div class="form-inline">
        <div class="input-group" data-widget="sidebar-search">
          <input class="form-control form-control-sidebar" type="search" placeholder="Search" aria-label="Search">
          <div class="input-group-append">
            <button class="btn btn-sidebar">
              <i class="fas fa-search fa-fw"></i>
            </button>
          </div>
        </div>
      </div>

      <!-- Sidebar Menu -->
      <nav class="mt-2">
        <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
          <!-- Add icons to the links using the .nav-icon class
               with font-awesome or any other icon font library -->
          <li class="nav-item menu-open">
            <a href="{{ url_for('dashboard') }}" class="nav-link active">
              <i class="nav-icon fas fa-tachometer-alt"></i>
              <p>
                Dashboard
                <i class="right fas fa-angle-left"></i>
              </p>
            </a>
          </li>
          <li class="nav-item">
            <a href="{{ url_for('books') }}" class="nav-link">
              <i class="nav-icon fas fa-table"></i>
              <p>
                Manage Books
              </p>
            </a>
          </li>
          <li class="nav-item">
            <a href="{{ url_for('logout') }}" class="nav-link">
              <i class="nav-icon fas fa-th"></i>
              <p>
                Logout
              </p>
            </a>
          </li>
          
        </ul>
      </nav>
      <!-- /.sidebar-menu -->
    </div>
    <!-- /.sidebar -->
  </aside>

  <!-- Content Wrapper. Contains page content -->
  <div class="content-wrapper">
    {% block body %}{% endblock %}
  </div>
  <!-- /.content-wrapper -->
  <footer class="main-footer">
    <strong>Copyright © 2014-2021 <a href="https://adminlte.io">AdminLTE.io</a>.</strong>
    All rights reserved.
    <div class="float-right d-none d-sm-inline-block">
      <b>Version</b> 3.2.0
    </div>
  </footer>

  <!-- Control Sidebar -->
  <aside class="control-sidebar control-sidebar-dark">
    <!-- Control sidebar content goes here -->
  </aside>
  <!-- /.control-sidebar -->
</div>
<!-- ./wrapper -->

<!-- jQuery -->
<script src="{{ url_for('static',filename='plugins/jquery/jquery.min.js') }}"></script>
<!-- Bootstrap 4 -->
<script src="{{ url_for('static',filename='plugins/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
<!-- AdminLTE App -->
<script src="{{ url_for('static',filename='js/adminlte.min.js') }}"></script>
<script>
  $(document).ready(function(){
    $('#addBook').click(function(){ 
      $('#bookModal').modal({
        backdrop: 'static',
        keyboard: false
      });		
      $("#bookModal").on("shown.bs.modal", function () {
        $('#bookForm')[0].reset();				
        $('.modal-title').html("<i class='fa fa-plus'></i> Add book");					
        $('#action').val('addBook');
        $('#save').val('Save');
      });
    });		
  });
  </script>
</body>
</html>
templates/books.html
//templates/books.html
{% extends 'layout.html' %}
 
{% block body %}
    <!-- Content Header (Page header) -->
    <div class="content-header">
      <div class="container-fluid">
        <div class="row mb-2">
          <div class="col-sm-6">
            <h1 class="m-0">Manage Books</h1>
          </div><!-- /.col -->
          <div class="col-sm-6">
            <ol class="breadcrumb float-sm-right">
              <li class="breadcrumb-item"><a href="#">Home</a></li>
              <li class="breadcrumb-item active">Manage Books</li>
            </ol>
          </div><!-- /.col -->
        </div><!-- /.row -->
      </div><!-- /.container-fluid -->
    </div>
    <!-- /.content-header -->

    <!-- Main content -->
    <section class="content">
      <div class="container-fluid">
        <div class="row">
			<h3>Book Listing</h3>
			<br>
			<div class="float-right mb-2 col-md-2">
				<button type="button" id="addBook" class="btn btn-info" title="Add book"><span class="glyphicon glyphicon-plus">Add Book</span></button>
			</div>
			<br><br>			
			<table class="table table-striped">
			<thead>
			  <tr>
				<th></th>
				<th>Book</th>
				<th>ISBN</th>
				<th></th>
				<th></th>
			  </tr>
			</thead>
			<tbody>
				{% for book in books %}			  
					<tr>
					  <td>
					  {% if book.picture %}
						  <img src="../static/images/{{book.picture}}" width="80" height="90">
					  {% else %}
						  <img src="../static/images/default.jpg" width="80" height="90">
					  {% endif %}					
					  </td>
					  <td>{{book.name}}</td>
					  <td>{{book.isbn}}</td>			
					  <td><a href="{{url_for('edit_book', bookid=book.bookid)}}" class="btn btn-primary">Edit</a></td>	
					  <td><a href="{{url_for('delete_book', bookid=book.bookid)}}" class="btn btn-danger">Delete</a></td>
					</tr> 
			  {% endfor %}	  
			  </tbody>
		  </table>	
        </div>
      </div><!-- /.container-fluid -->
    </section>
    <!-- /.content -->
	<div class="modal fade" id="bookModal">
        <div class="modal-dialog">
			<form method="post" id="bookForm" action="{{ url_for('save_book')}}" enctype="multipart/form-data">
			<div class="modal-content">
				<div class="modal-header">
				<h4 class="modal-title">Add New</h4>
				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
					<span aria-hidden="true">×</span>
				</button>
				</div>
				<div class="modal-body">
					<div class="form-group">							
						<label for="book" class="control-label">Book</label>							
						<input type="text" name="name" id="name" autocomplete="off" class="form-control" placeholder="book name"/>
										
					</div>
					<div class="form-group">							
						<label for="book" class="control-label">ISBN No</label>							
						<input type="text" name="isbn" id="isbn" autocomplete="off" class="form-control" placeholder="isbn name"/>		
					</div>
					<div class="form-group">
						<label>File Upload</label>
						<input type="file" name="uploadFile" accept=".jpg, .png" />
					</div>

				</div>
				<div class="modal-footer justify-content-between">
				<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
				<input type="hidden" name="action" id="action" value="" />
				<input type="submit" name="save" id="save" class="btn btn-primary" value="Save changes" />
				</div>
			</div>
			</form>
          <!-- /.modal-content -->
        </div>
        <!-- /.modal-dialog -->
    </div>
{% endblock %}
templates/books.html
//templates/books.html
{% extends 'layout.html' %}
 
{% block body %}
    <!-- Content Header (Page header) -->
    <div class="content-header">
      <div class="container-fluid">
        <div class="row mb-2">
          <div class="col-sm-6">
            <h1 class="m-0">Manage Books</h1>
          </div><!-- /.col -->
          <div class="col-sm-6">
            <ol class="breadcrumb float-sm-right">
              <li class="breadcrumb-item"><a href="#">Home</a></li>
              <li class="breadcrumb-item active">Manage Books</li>
            </ol>
          </div><!-- /.col -->
        </div><!-- /.row -->
      </div><!-- /.container-fluid -->
    </div>
    <!-- /.content-header -->

    <!-- Main content -->
    <section class="content">
      <div class="container-fluid">
        <div class="row">
            <div class="col-md-12">
			<h3>Book Edit</h3>
            <form method="post" id="bookForm" action="{{ url_for('save_book')}}">
                <div class="card-body">               
                                       
                        <div class="form-group">							
                            <label for="book" class="control-label">Book</label>							
                            <input type="text" name="name" id="name" autocomplete="off" class="form-control" placeholder="book name" value="{{books.name}}"/>                             
                        </div>                    
                        <div class="form-group">							
                            <label for="book" class="control-label">ISBN No</label>							
                            <input type="text" name="isbn" id="isbn" autocomplete="off" class="form-control" placeholder="isbn name" value="{{books.isbn}}"/>                                        
                        </div>    
                        <div class="form-group">							
                            <label for="book" class="control-label">Picture</label>							
                            <img src="static/images/{{books.picture}}" width="360"/>
                        </div>                               
                        
                        <input type="hidden" name="bookid" id="bookid" value="{{books.bookid}}" />									
                        <input type="hidden" name="action" id="action" value="updateBook" />
                        <input type="submit" name="save" id="save" class="btn btn-info" value="Save" />                    
                </div>
            </form>
            </div>
        </div>
      </div><!-- /.container-fluid -->
    </section>
{% endblock %}
templates/edit_books.html
//templates/edit_books.html
{% extends 'layout.html' %}
 
{% block body %}
    <!-- Content Header (Page header) -->
    <div class="content-header">
      <div class="container-fluid">
        <div class="row mb-2">
          <div class="col-sm-6">
            <h1 class="m-0">Manage Books</h1>
          </div><!-- /.col -->
          <div class="col-sm-6">
            <ol class="breadcrumb float-sm-right">
              <li class="breadcrumb-item"><a href="#">Home</a></li>
              <li class="breadcrumb-item active">Manage Books</li>
            </ol>
          </div><!-- /.col -->
        </div><!-- /.row -->
      </div><!-- /.container-fluid -->
    </div>
    <!-- /.content-header -->

    <!-- Main content -->
    <section class="content">
      <div class="container-fluid">
        <div class="row">
            <div class="col-md-12">
			<h3>Book Edit</h3>
            <form method="post" id="bookForm" action="{{ url_for('save_book')}}">
                <div class="card-body">               
                                       
                        <div class="form-group">							
                            <label for="book" class="control-label">Book</label>							
                            <input type="text" name="name" id="name" autocomplete="off" class="form-control" placeholder="book name" value="{{books.name}}"/>                             
                        </div>                    
                        <div class="form-group">							
                            <label for="book" class="control-label">ISBN No</label>							
                            <input type="text" name="isbn" id="isbn" autocomplete="off" class="form-control" placeholder="isbn name" value="{{books.isbn}}"/>                                        
                        </div>    
                        <div class="form-group">							
                            <label for="book" class="control-label">Picture</label>							
                            <img src="static/images/{{books.picture}}" width="360"/>
                        </div>                               
                        
                        <input type="hidden" name="bookid" id="bookid" value="{{books.bookid}}" />									
                        <input type="hidden" name="action" id="action" value="updateBook" />
                        <input type="submit" name="save" id="save" class="btn btn-info" value="Save" />                    
                </div>
            </form>
            </div>
        </div>
      </div><!-- /.container-fluid -->
    </section>
{% endblock %}
run (venv) C:\flask_dev\flaskreact>flask run

Related Post